Găsim bug-uri în LLVM 8 cu ajutorul analizoarei PVS-Studio

Găsirea erorilor în LLVM 8 cu ajutorul analizei PVS-Studio
Au trecut mai bine de doi ani de la ultima verificare a codului proiectului LLVM cu ajutorul analizei noastre PVS-Studio. Să ne asigurăm că PVS-Studio rămâne un instrument de frunte pentru identificarea erorilor și vulnerabilităților potențiale. Pentru aceasta, vom verifica și găsi noi erori în versiunea LLVM 8.0.0.

Articolul care trebuie să fie scris

Sincer, nu mi-a plăcut să scriu acest articol. Nu este interesant să scrii despre un proiect pe care l-am verificat deja de mai multe ori (1, 2, 3). De preferat ar fi să scriu despre ceva nou, dar nu am de ales.

De fiecare dată când apare o nouă versiune LLVM sau se actualizează Clang Static Analyzer, primim în email întrebări de genul:

Uitați, noua versiune Clang Static Analyzer a învățat să găsească noi erori! Mi se pare că relevanța utilizării PVS-Studio scade. Clang găsește mai multe erori decât înainte și se apropie de capabilitățile PVS-Studio. Ce părere aveți despre asta?

La această întrebare, mereu am dorit să răspund ceva de genul:

Nici noi nu stăm degeaba! Am îmbunătățit semnificativ capabilitățile analizei PVS-Studio. Așa că nu vă faceți griji, continuăm să fim lideri, ca și înainte.

Din păcate, acesta este un răspuns slab. Nu conține dovezi. Și tocmai de aceea scriu acest articol acum. Așadar, proiectul LLVM a fost verificat din nou și au fost găsite diverse erori. Cele care mi s-au părut interesante, le voi demonstra acum. Aceste erori nu pot fi găsite de Clang Static Analyzer (sau este extrem de incomod să le găsești cu el). Dar noi putem. De fapt, am găsit și am scris toate aceste erori într-o singură seară.

Dar scrierea articolului s-a întins pe câteva săptămâni. Nu mă puteam face să pun totul în formă de text :).

Apropo, dacă vă interesează ce tehnologii sunt utilizate în analiza PVS-Studio pentru identificarea erorilor și vulnerabilităților potențiale, vă invit să vă familiarizați cu acest articol.

Diagnosticări Noi și Vechi

Așa cum a fost menționat, acum aproximativ doi ani proiectul LLVM a fost verificat din nou, iar erorile găsite au fost corectate. Acum, în acest articol va fi prezentată o nouă serie de erori. De ce au fost găsite erori noi? Există trei motive pentru aceasta:

  1. Proiectul LLVM este în continuă dezvoltare, vechiul cod este modificat, iar noul cod apare. Este evident că în codul modificat și scris există erori noi. Acest lucru demonstrează bine că analiza statică trebuie aplicată în mod regulat, nu ocazional. Articolele noastre ilustrează bine capacitățile analizei PVS-Studio, dar aceasta nu este legată de îmbunătățirea calității codului sau de reducerea costurilor de corectare a erorilor. Folosiți analiza statică a codului în mod regulat!
  2. Îmbunătățim și rafinăm diagnosticele existente. Prin urmare, analizatorul poate identifica erori care nu au fost observate la verificările anterioare.
  3. În PVS-Studio au apărut diagnostice noi, care nu existau acum 2 ani. Am decis să le evidențiez într-o secțiune separată pentru a arăta dezvoltarea PVS-Studio.

Defecte identificate de diagnosticele care existau acum 2 ani

Fragment N1: Copy-Paste

static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
  if (Name == "addcarryx.u32" || // Adăugat în 8.0
    ....
    Name == "avx512.mask.cvtps2pd.128" || // Adăugat în 7.0
    Name == "avx512.mask.cvtps2pd.256" || // Adăugat în 7.0
    Name == "avx512.cvtusi2sd" || // Adăugat în 7.0
    Name.startswith("avx512.mask.permvar.") || // Adăugat în 7.0     // <=
    Name.startswith("avx512.mask.permvar.") || // Adăugat în 7.0     // <=
    Name == "sse2.pmulu.dq" || // Adăugat în 7.0
    Name == "sse41.pmuldq" || // Adăugat în 7.0
    Name == "avx2.pmulu.dq" || // Adăugat în 7.0
  ....
}

Avertizare PVS-Studio: V501 [CWE-570] Există sub-expresii identice ‘Name.startswith(«avx512.mask.permvar.»)’ atât la stânga, cât și la dreapta operatorului ‘||’. AutoUpgrade.cpp 73

Se verifică de două ori dacă numele începe cu subșirul «avx512.mask.permvar.». La a doua verificare, se dorea evident scrierea altceva, dar s-a uitat să corecteze textul copiat.

Fragment N2: Typo

enum CXNameRefFlags {
  CXNameRange_WantQualifier = 0x1,
  CXNameRange_WantTemplateArgs = 0x2,
  CXNameRange_WantSinglePiece = 0x4
};

void AnnotateTokensWorker::HandlePostPonedChildCursor(
    CXCursor Cursor, unsigned StartTokenIndex) {
  const auto flags = CXNameRange_WantQualifier | CXNameRange_WantQualifier;
  ....
}

Avertizare PVS-Studio: V501 Există sub-expresii identice ‘CXNameRange_WantQualifier’ atât la stânga, cât și la dreapta operatorului ‘|’. CIndex.cpp 7245

Din cauza unei erori de tipar, aceeași constantă denumită este utilizată de două ori CXNameRange_WantQualifier.

Fragment N3: Confuzie cu prioritățile operatorilor

int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
  ....
  if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
    return 0;
  ....
}

Avertizare PVS-Studio: V502 [CWE-783] Poate că operatorul ‘?:’ funcționează într-un mod diferit față de a fost așteptat. Operatorul ‘?:’ are o prioritate mai mică decât operatorul ‘==’. PPCTargetTransformInfo.cpp 404

Din punctul meu de vedere, aceasta este o eroare foarte frumoasă. Da, știu că am o viziune ciudată asupra frumuseții :)

Acum, conform priorităților operatorilor, expresia este calculată astfel:

(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0

Din punct de vedere practic, această condiție nu are sens, deoarece poate fi simplificată la:

(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())

Aceasta este o eroare evidentă. Cel mai probabil, 0/1 au fost comparați cu o variabilă Index. Pentru a corecta codul trebuie adăugate paranteze în jurul operatorului ternar:

if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))

Apropo, operatorul ternar este foarte periculos și provoacă erori logice. Fii foarte atent cu el și nu ezita să folosești paranteze. Detaliile acestei teme le-am discutat aici, în capitolul „Ferește-te de operatorul ?: și încadrează-l în paranteze”.

Fragment N4, N5: Pointeur nul

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("nu se poate cast '") + LHS->getAsString() +
                    "' în string");
    return nullptr;
  }
  ....
}

Avertizare PVS-Studio: V522 [CWE-476] Dereferirea pointerului nul „LHS” ar putea avea loc. TGParser.cpp 2152

Dacă pointerul LHS se dovedește a fi nul, ar trebui să fie emis un avertisment. Cu toate acestea, în loc de aceasta, va avea loc dereferirea acestui pointer nul: LHS->getAsString().

Aceasta este o situație tipică, când o eroare este ascunsă în handler-ul de erori, deoarece nimeni nu le testează. Analizatorii statici verifică tot codul accesibil, indiferent de câte ori este utilizat. Este un exemplu foarte bun de cum analiza statică completează alte metode de testare și protecție împotriva erorilor.

O eroare similară în manipularea pointerului RHS a fost comisă în codul puțin mai jos: V522 [CWE-476] Dereferirea pointerului nul „RHS” ar putea avea loc. TGParser.cpp 2186

Fragment N6: Utilizarea pointerului după mutare

static Expected
ExtractBlocks(....)
{
  ....
  std::unique_ptr ProgClone = CloneModule(BD.getProgram(), VMap);
  ....
  BD.setNewProgram(std::move(ProgClone));                                // getFunction(MisCompFunctions[i].first);  // <=
    assert(NewF && "Funcția nu a fost găsită??");
    MiscompiledFunctions.push_back(NewF);
  }
  ....
}

Avertisment PVS-Studio: V522 [CWE-476] Dereferirea pointerului nul „ProgClone” ar putea avea loc. Miscompilation.cpp 601

La început, pointerul inteligent ProgClone își pierde proprietatea asupra obiectului:

BD.setNewProgram(std::move(ProgClone));

De fapt, acum ProgClone — este un pointer nul. Așadar, mai jos ar trebui să aibă loc dereferirea pointer-ului nul:

Funcție *NewF = ProgClone->getFunction(MisCompFunctions[i].first);

Dar, de fapt, acest lucru nu se va întâmpla! Observați că ciclul, de fapt, nu se execută.

La începutul containerului MiscompiledFunctions este golit:

MiscompiledFunctions.clear();

Apoi, dimensiunea acestui container este utilizată în condiția ciclului:

for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {

Se vede ușor că ciclul nu se pornește. Cred că acesta este și o greșeală, iar codul ar trebui scris într-un alt mod.

Se pare că am dat peste aceeași celebre coerență a greșelilor! O greșeală maschează alta :).

Fragment N7: Utilizarea pointer-ului după mutare

static Expected TestOptimizer(BugDriver &BD, std::unique_ptr Test,
                                    std::unique_ptr Safe) {
  outs() << "  Optimizarea funcțiilor testate: ";
  std::unique_ptr Optimized =
      BD.runPassesOn(Test.get(), BD.getPassesToRun());
  if (!Optimized) {
    errs() << " Eroare în rularea acestei secvențe de treceri"
           << " pe programul de intrare!n";
    BD.setNewProgram(std::move(Test));                       // <=
    BD.EmitProgressBitcode(*Test, "pass-error", false);      // <=
    if (Error E = BD.debugOptimizerCrash())
      return std::move(E);
    return false;
  }
  ....
}

Avertisment PVS-Studio: V522 [CWE-476] Dereferirea pointer-ului nul ‘Test’ ar putea avea loc. Miscompilation.cpp 709

Din nou aceeași situație. La început, conținutul obiectului este mutat, iar apoi este utilizat ca și cum nimic nu s-ar fi întâmplat. Întâlnesc din ce în ce mai des această situație în codul programelor, după ce semantică mutării a fost introdusă în C++. Tocmai de aceea îmi place C++! Apar tot mai multe moduri de a-ți trage singur un glonț în picior. Analizatorul PVS-Studio va avea întotdeauna muncă :).

Fragment N8: Pointer nul

void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
  uint32_t TypeId = Symbol.getTypeId();
  auto Type = Symbol.getSession().getSymbolById(TypeId);
  if (Type)
    Printer << "";
  else
    Type->dump(*this);
}

Avertisment PVS-Studio: V522 [CWE-476] Dereferirea pointer-ului nul ‘Type’ ar putea avea loc. PrettyFunctionDumper.cpp 233

Pe lângă handler-ii de erori, de obicei nu se testează nici funcțiile de printare a datelor pentru depanare. Aceasta este exact o astfel de situație. Funcția așteaptă utilizatorul, care, în loc să își rezolve problemele, va fi nevoit să se ocupe de repararea ei.

Corect:

if (Type)
  Type->dump(*this);
else
  Printer << "";

Fragment N9: Pointer nul

void SearchableTableEmitter::collectTableEntries(
    GenericTable &Table, const std::vector &Items) {
  ....
  RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
  if (!Ty)                                                              // getAsString() + " vs. " +                       // getType()->getAsString());
   ....
}

Atenție PVS-Studio: V522 [CWE-476] Referința la pointerul null ‘Ty’ ar putea avea loc. SearchableTableEmitter.cpp 614

Cred că este deja evident și nu necesită explicații.

Fragment N10: Typo

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;
}

Avertizare PVS-Studio: V570 Variabila ‘Identifier->Type’ se atribuie sieși. FormatTokenLexer.cpp 249

Nu are sens să îi atribui variabilei valoarea sa. Probabil că doreați să scrieți:

Identifier->Type = Question->Type;

Fragment N11: break suspect

void SystemZOperand::print(raw_ostream &OS) const {
  switch (Kind) {
    break;
  case KindToken:
    OS << "Token:" << getToken();
    break;
  case KindReg:
    OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
    break;
  ....
}

Avertizare PVS-Studio: V622 [CWE-478] Luați în considerare inspectarea instrucțiunii ‘switch’. Este posibil să lipsească operatorul ‘case’ inițial. SystemZAsmParser.cpp 652

Există un operator foarte suspect la început break. Aici nu ați omis să scrieți ceva?

Fragment N12: Verificarea pointerului după dereferire

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");
  ....
}

Avertizare PVS-Studio: V595 [CWE-476] Pointerul ‘Callee’ a fost utilizat înainte de a fi verificat împotriva nullptr. Verificați liniile: 172, 174. AMDGPUInline.cpp 172

Pointer Callee la început se dereferă în momentul apelării funcției getTTI.

Și apoi se dovedește că acest pointer ar trebui să fie verificat pentru egalitate nullptr:

if (!Callee || Callee->isDeclaration())

Dar deja este prea târziu…

Fragment N13 — N…: Verificarea pointerului după dereferire

Situația discutată în fragmentul de cod anterior nu este unică. O întâlniți aici:

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()) {               // <=
  ....
}

Atenție PVS-Studio: V595 [CWE-476] Pointerul ‘CalleeFn’ a fost utilizat înainte de a fi verificat împotriva nullptr. Verificați liniile: 1079, 1081. SimplifyLibCalls.cpp 1079

Și aici:

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());     
  ....
}

Avertisment PVS-Studio: V595 [CWE-476] Punctul ‘ND’ a fost utilizat înainte de a fi verificat împotriva nullptr. Verificați liniile: 532, 534. SemaTemplateInstantiateDecl.cpp 532

Și aici:

  • V595 [CWE-476] Punctul ‘U’ a fost utilizat înainte de a fi verificat împotriva nullptr. Verificați liniile: 404, 407. DWARFFormValue.cpp 404
  • V595 [CWE-476] Punctul ‘ND’ a fost utilizat înainte de a fi verificat împotriva nullptr. Verificați liniile: 2149, 2151. SemaTemplateInstantiate.cpp 2149

Apoi, mi-a devenit neinteresant să studiez avertismentele cu numărul V595. Așa că nu știu dacă mai există alte erori de acest tip, în afară de cele enumerate aici. Cel mai probabil există.

Fragment N17, N18: Shift suspect

static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
                                           uint64_t &Encoding) {
  ....
  unsigned Size = RegSize;
  ....
  uint64_t NImms = ~(Size-1) << 1;
  ....
}

Avertizare PVS-Studio: V629 [CWE-190] Considerați inspectarea expresiei ‘~(Size — 1) << 1’. Shift bit a valorii de 32 de biți cu o extindere ulterioară la tipul de 64 de biți. AArch64AddressingModes.h 260

Este posibil ca aceasta să nu fie o eroare, iar codul să funcționeze exact așa cum a fost intenționat. Dar este cu siguranță un loc foarte suspect, și trebuie să fie verificat.

Să presupunem că variabila Size este 16, iar autorul codului a planificat să obțină în variabila NImms valoarea:

1111111111111111111111111111111111111111111111111111111111100000

Cu toate acestea, de fapt, va rezulta valoarea:

0000000000000000000000000000000011111111111111111111111111100000

Ideea este că toate calculele se desfășoară folosind tipul întreg fără semn de 32 de biți. Și abia apoi, acest tip întreg fără semn de 32 de biți va fi extins implicit la uint64_t. În acest proces, biții superiori vor fi zero.

Situația poate fi corectată astfel:

uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;

Situație similară: V629 [CWE-190] Considerați inspectarea expresiei ‘Immr << 6’. Shift bit a valorii de 32 de biți cu o extindere ulterioară la tipul de 64 de biți. AArch64AddressingModes.h 269

Fragment N19: Cuvânt cheie lipsă altfel?

void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
  ....
  if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
    
    continue;
  } if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {    
    Op.addRegWithFPInputModsOperands(Inst, 2);
  } else if (Op.isDPPCtrl()) {
    Op.addImmOperands(Inst, 1);
  } else if (Op.isImm()) {
    
    OptionalIdx[Op.getImmTy()] = I;
  } else {
    llvm_unreachable("Tip de operand invalid");
  }
  ....
}

Avertizare PVS-Studio: V646 [CWE-670] Considerați inspectarea logica aplicației. Este posibil ca cuvântul cheie 'else' să fie lipsă. AMDGPUAsmParser.cpp 5655

Nu există nicio eroare aici. Deoarece blocul then al primului if se încheie cu continue, deci nu contează, dacă există o cuvânt cheie altfel sau nu. În orice caz, codul va funcționa la fel. Totuși, omisă altfel face codul mai neclar și periculos. Dacă ulterior continue dispare, codul va începe să funcționeze complet diferit. În opinia mea, este mai bine să adăugăm altfel.

Fragment N20: Patru greșeli de tipar asemănătoare

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;
}

Avertizări PVS-Studio:

  • V655 [CWE-480] String-urile au fost concatenat dar nu sunt utilizate. Considerați să inspectați expresia ‘Result + Name.str()’. Symbol.cpp 32
  • V655 [CWE-480] String-urile au fost concatenat dar nu sunt utilizate. Considerați să inspectați expresia ‘Result + «(ObjC Class) » + Name.str()’. Symbol.cpp 35
  • V655 [CWE-480] String-urile au fost concatenat dar nu sunt utilizate. Considerați să inspectați expresia ‘Result + «(ObjC Class EH) » + Name.str()’. Symbol.cpp 38
  • V655 [CWE-480] String-urile au fost concatenat dar nu sunt utilizate. Considerați să inspectați expresia ‘Result + «(ObjC IVar) » + Name.str()’. Symbol.cpp 41

Din greșeală, operatorul += este înlocuit cu operatorul +. Ca urmare, se formează construcții lipsite de sens.

Fragment N21: Comportament nedefinit

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();
    }
  }
}

Încercați să găsiți singuri codul periculos. Iată o imagine pentru a vă distrage atenția, astfel încât să nu vă uitați imediat la răspuns:

Găsirea erorilor în LLVM 8 cu ajutorul analizei PVS-Studio

Avertizare PVS-Studio: V708 [CWE-758] Construcție periculoasă utilizată: ‘FeaturesMap[Op] = FeaturesMap.size()’, unde ‘FeaturesMap’ este de tip 'map'. Acest lucru poate duce la comportament nedefinit. RISCVCompressInstEmitter.cpp 490

Linia problematică:

FeaturesMap[Op] = FeaturesMap.size();

Dacă elementul Op nu este găsit, atunci se creează un nou element în hartă și acolo este înregistrat numărul de elemente din această hartă. Numai că nu se știe dacă va fi apelată funcția dimensiune înainte sau după adăugarea noului element.

Fragment N22-N24: Atribuiri repetate

Eroare 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;
  }
  ....
}

Avertizare PVS-Studio: V519 [CWE-563] Variabila ‘NType’ este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 1663, 1664. MachOObjectFile.cpp 1664

Cred că în acest caz nu este o eroare reală. Doar o atribuire redundantă. Dar tot este o greșeală.

Similar:

  • V519 [CWE-563] Variabila ‘B.NDesc’ este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Variabila este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 59, 61. coff2yaml.cpp 61

Fragment N25-N27: Încă atribuiri repetate

Acum să analizăm o variantă puțin diferită de atribuiri repetitive.

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;
  ....
}

Avertisment PVS-Studio: V519 [CWE-563] Variabila ‘Alignment’ este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 1158, 1160. LoadStoreVectorizer.cpp 1160

Acesta este un cod foarte ciudat, care aparent conține o eroare logică. La început, variabila Alignment primește o valoare în funcție de condiție. Apoi are loc din nou o atribuire, dar acum fără nicio verificare.

Situații similare pot fi observate aici:

  • V519 [CWE-563] Variabila ‘Effects’ este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] Variabila ‘ExpectNoDerefChunk’ este atribuită valorile de două ori consecutiv. Poate că aceasta este o greșeală. Verificați liniile: 4970, 4973. SemaType.cpp 4973

Fragment N28: Condiție întotdeauna adevărată

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) // suport pentru instrucțiunea PAUSE             // <=
      break;
  }
  ....
}

Avertizare PVS-Studio: V547 [CWE-571] Expresia ‘nextByte != 0x90’ este întotdeauna adevărată. X86DisassemblerDecoder.cpp 379

Verificarea nu are sens. Variabila nextByte este întotdeauna diferită de valoarea 0x90, ceea ce rezultă din verificarea anterioară. Este o eroare logică.

Fragment N29 — N…: Condiții întotdeauna adevărate/false

Analizatorul generează multe avertismente că întreaga condiție (V547) sau o parte a acesteia (V560) întotdeauna adevărat sau fals. Adesea, acestea nu sunt erori reale, ci pur și simplu cod neglijent, rezultatul desfășurării macrocomenzilor și așa mai departe. Cu toate acestea, are sens să ne uităm la toate aceste avertismente, deoarece din când în când apar adevărate erori logice. De exemplu, această porțiune de cod este suspectă:

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;
  ....
}

Avertizare PVS-Studio: V560 [CWE-570] O parte a expresiei condiționale este întotdeauna falsă: RegNo == 0xe. ARMDisassembler.cpp 939

Constanta 0xE este valoarea 14 în sistemul decimal. Verificarea RegNo == 0xe nu are sens, deoarece dacă RegNo > 13, funcția își va încheia execuția.

Au fost multe alte avertismente marcate cu identifcatorul V547 și V560, dar, la fel ca și în cazul lui V595, a studia aceste avertismente nu mi-a fost de interes. Era deja clar că am suficiente materiale pentru a scrie un articol :). Prin urmare, nu se știe câte erori de acest tip pot fi identificate în LLVM folosind PVS-Studio.

Voi da un exemplu de ce studierea acestor declanșări este plictisitoare. Analyzerul are dreptate, emitand un avertisment pentru următorul cod. Dar nu este o eroare.

bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
                                          tok::TokenKind ClosingBraceKind) {
  bool HasError = false;
  ....
  HasError = true;
  if (!ContinueOnSemicolons)
    return !HasError;
  ....
}

Avertisment PVS-Studio: V547 [CWE-570] Expresia ‘!HasError’ este întotdeauna falsă. UnwrappedLineParser.cpp 1635

Fragment N30: Return suspect

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();
  }
  ....
}

Avertizare PVS-Studio: V612 [CWE-670] Un ‘return’ necondiționat într-un ciclu. R600OptimizeVectorRegisters.cpp 63

Aceasta este ori o eroare, ori o tehnică specifică destinată să explice ceva programatorilor care citesc codul. Această construcție nu-mi oferă nicio explicație și pare foarte suspectă. Ar fi mai bine să nu scrieți așa :).

Obosit? Atunci este timpul să prepari o ceai sau o cafea.

Găsirea erorilor în LLVM 8 cu ajutorul analizei PVS-Studio

Defecte identificate prin noile diagnostice

Cred că 30 de declanșări ale vechilor diagnostice sunt suficiente. Hai să vedem acum ce lucruri interesante pot fi găsite cu noile diagnostice care au apărut în analizatorul după partea anterioară, că în acest sistem se folosesc bifurcații speciale: verificare. În această perioadă, analizatorul C++ a adăugat 66 de diagnostice generale.

Fragment N31: Cod inaccesibil

Eroare 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();
}

Avertizare PVS-Studio: V779 [CWE-561] Cod inaccesibil detectat. Este posibil să existe o eroare. ExecutionUtils.cpp 146

După cum vedeți, ambele ramuri ale operatorului if se încheie cu apelul operatorului return. În consecință, containerul CtorDtorsByPriority nu va fi niciodată curățat.

Fragment N32: Cod inaccesibil

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(), "tip sumar neașteptat");
  }
  Lex.setIgnoreColonInIdentifiers(false);                      // <=
  return false;
}

Avertisment PVS-Studio: V779 [CWE-561] Cod inaccesibil detectat. Este posibil să existe o eroare. LLParser.cpp 835

O situație interesantă. Să ne uităm mai întâi la acest loc:

return ParseTypeIdEntry(SummaryID);
break;

La prima vedere, pare că nu există erori aici. Se pare că operatorul break este de prisos aici și poate fi eliminat. Totuși, nu e chiar așa.

Analizatorul emite un avertisment pentru liniile:

Lex.setIgnoreColonInIdentifiers(false);
return false;

Și într-adevăr, acest cod este inaccesibil. Toate cazurile din switch se finalizează cu apelul operatorului return. Și acum, singuraticul fără sens break nu mai pare atât de inofensiv! Poate că una dintre ramuri ar trebui să se sfârșească cu break, nu cu return?

Fragment N33: Resetarea aleatorie a bitilor superiori

unsigned getStubAlignment() override {
  if (Arch == Triple::systemz)
    return 8;
  else
    return 1;
}

Expected
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
                             const SectionRef &Section,
                             bool IsCode) {
  ....
  uint64_t DataSize = Section.getSize();
  ....
  if (StubBufSize > 0)
    DataSize &= ~(getStubAlignment() - 1);
  ....
}

Avertizare PVS-Studio: V784 Dimensiunea măștii de biți este mai mică decât dimensiunea primului operand. Acest lucru va cauza pierderea bitilor superiori. RuntimeDyld.cpp 815

Observați că funcția getStubAlignment returnează tipul unsigned. Să calculăm valoarea expresiei, presupunând că funcția va returna valoarea 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFF8‬u

Acum observați că variabila DataSize are un tip fără semn de 64 de biți. Așadar, atunci când se execută operația DataSize & 0xFFFFFFF8‬u, toți cei treizeci și doi de biți superiori vor fi setati la zero. Cel mai probabil, acest lucru nu era intenția programatorului. Bănuiesc că dorea să calculeze: DataSize & 0xFFFFFFFFFFFFFFF8‬u.

Pentru a corecta eroarea, ar trebui să scrieți astfel:

DataSize &= ~(static_cast(getStubAlignment()) - 1);

Sau așa:

DataSize &= ~(getStubAlignment() - 1ULL);

Fragment N34: Conversia de tip explicită eșuată

template 
void scaleShuffleMask(int Scale, ArrayRef Mask,
                      SmallVectorImpl &ScaledMask) {
  assert(0 < Scale && "Factor de scalare neașteptat");
  int NumElts = Mask.size();
  ScaledMask.assign(static_cast(NumElts * Scale), -1);
  ....
}

Avertizare PVS-Studio: V1028 [CWE-190] Posibilă depășire. Luați în considerare conversia operanzilor operatorului ‘NumElts * Scale’ la tipul ‘size_t’, nu rezultatul. X86ISelLowering.h 1577

Conversia de tip explicit este folosită pentru a preveni depășirea în timpul înmulțirii variabilelor de tip int. Cu toate acestea, aici conversia de tip explicit nu protejează împotriva depășirii. La început, variabilele vor fi înmulțite, iar apoi rezultatul de 32 de biți al înmulțirii va fi extins la tip size_t.

Fragment N35: Copy-Paste eșuat

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;
  }
  ....
}

V778 [CWE-682] Au fost găsite două fragmente de cod similare. Poate că aceasta este o greșeală și variabila ‘Op1’ ar trebui folosită în loc de ‘Op0’. InstCombineCompares.cpp 5507

Această nouă diagnoză interesantă identifică situații în care un fragment de cod a fost copiat și au început să fie schimbate anumite nume, dar într-un loc nu a fost corectat.

Observați că în al doilea bloc s-a schimbat Op0 pe Op1. Dar într-un loc nu a fost corectat. Cel mai probabil, ar fi trebuit scris așa:

if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
  I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
  return &I;
}

Fragment N36: Confuzie în variabile

struct Status {
  unsigned Mask;
  unsigned Mode;

  Status() : Mask(0), Mode(0){};

  Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
    Mode &= Mask;
  };
  ....
};

Avertizare PVS-Studio: V1001 [CWE-563] Variabila ‘Mode’ este asignată, dar nu este folosită până la finalul funcției. SIModeRegister.cpp 48

Este foarte periculos să dai argumentelor funcțiilor aceleași nume ca și membrilor clasei. Se poate confunda foarte ușor. Acesta este un astfel de caz. Această expresie nu are sens:

Mode &= Mask;

Se schimbă argumentul funcției. Și atât. Acest argument nu mai este folosit. Cel mai probabil, ar fi trebuit scris așa:

Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
  this->Mode &= Mask;
};

Fragment N37: Confuzie în variabile

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;
}

Avertizare PVS-Studio: V1001 [CWE-563] Variabila ‘Size’ este atribuită, dar nu este utilizată la sfârșitul funcției. Object.cpp 424

Situația este similară celei anterioare. Ar trebui să fie scris:

this->Size += this->EntrySize;

Fragment N38-N47: Punctul de referință a fost uitat pentru a fi verificat

Anterior am analizat exemple de declanșare a diagnosticului V595. Esența acesteia este că punctul de referință este dereferit de la început, iar abia apoi verificat. Diagnostica tânără V1004 este inversă ca semnificație, dar de asemenea descoperă foarte multe erori. Aceasta identifică situațiile în care punctul de referință a fost verificat de la început și apoi a fost uitat. Să analizăm astfel de cazuri găsite în interiorul 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());  // <=
  ....
}

Avertizare PVS-Studio: V1004 [CWE-476] Punctul de referință ‘Ptr’ a fost utilizat în mod nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 729, 738. TargetTransformInfoImpl.h 738

Variabilă Ptr poate fi egal cu nullptr, ceea ce este indicat de verificare:

if (Ptr != nullptr)

Cu toate acestea, mai jos acest punct de referință este dereferit deja fără verificare prealabilă:

auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());

Să analizăm un alt caz similar.

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(); // <=
  ....
}

Avertizare PVS-Studio: V1004 [CWE-476] Punctul de referință ‘FD’ a fost utilizat în mod nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 3228, 3231. CGDebugInfo.cpp 3231

Notă asupra punctului de referință FD. Sunt sigur că problema este bine vizibilă și nu sunt necesare explicații speciale.

Și încă:

static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
                                         Value *&BasePtr,
                                         const DataLayout &DL) {
  PointerType *PtrTy = dyn_cast(Ptr.getType());
  if (!PtrTy) {                                                   // getPointerAddressSpace());     // <=
  ....
}

Avertizare PVS-Studio: V1004 [CWE-476] Punctul de tip ‘PtrTy’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 960, 965. InterleavedLoadCombinePass.cpp 965

Cum să vă protejați de astfel de erori? Fiți atenți la revizuirea codului și folosiți un analizator static de cod PVS-Studio pentru verificări periodice.

Aduce alte fragmente de cod cu acest tip de erori nu are sens. Voi lăsa în articol doar lista de avertizări:

  • V1004 [CWE-476] Punctul de tip ‘Expr’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] Punctul de tip ‘PI’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] Punctul de tip ‘StatepointCall’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] Punctul de tip ‘RV’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] Punctul de tip ‘CalleeFn’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] Punctul de tip ‘TC’ a fost utilizat nesigur după ce a fost verificat împotriva nullptr. Verificați liniile: 1819, 1824. Driver.cpp 1824

Fragmentul N48-N60: Nu este critic, dar este un defect (posibilă scurgere de memorie)

std::unique_ptr createISelMutator() {
  ....
  std::vector<std::unique_ptr> Strategies;
  Strategies.emplace_back(
      new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
  ....
}

Avertizare PVS-Studio: V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ‘Strategies’ de metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-isel-fuzzer.cpp 58

Pentru adăugarea unui element la sfârșitul unui container de tip std::vector<std::unique_ptr> nu se poate scrie pur și simplu xxx.push_back(new X), deoarece nu există o conversie implicită din X* în std::unique_ptr.

O soluție comună este scrierea xxx.emplace_back(new X), deoarece se compilează: metoda emplace_back construiește elementul direct din argumente și, prin urmare, poate utiliza constructori expliciți.

Aceasta nu este sigur. Dacă vectorul este plin, va avea loc re-alocarea memoriei. Operația de re-alocare poate eșua, generând o excepție std::bad_alloc. În acest caz, pointerul va fi pierdut, iar obiectul creat nu va fi niciodată eliminat.

O soluție sigură este crearea unui unique_ptr, care va deține pointerul până când vectorul va încerca să re-aloce memoria:

xxx.push_back(std::unique_ptr(new X))

Începând cu C++14, se poate folosi ‘std::make_unique’:

xxx.push_back(std::make_unique())

Această tipă de defect nu este critică pentru LLVM. Dacă nu se poate aloca memorie, atunci activitatea compilatorului va fi pur și simplu oprită. Cu toate acestea, pentru aplicațiile cu o durata de funcționare, care nu pot pur și simplu să se închidă dacă nu s-a putut aloca memorie, aceasta poate fi o eroare neplăcută.

Așadar, deși acest cod nu reprezintă un pericol practic pentru LLVM, am considerat util să discut despre acest model de erori și cum analistul PVS-Studio a învățat să-l identifice.

Alte avertizări de acest tip:

  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Passes’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. PassManager.h 546
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘AAs’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. AliasAnalysis.h 324
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Entries’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘AllEdges’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. CFGMST.h 268
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘VMaps’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Records’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. FDRLogBuilder.h 30
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘PendingSubmodules’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. ModuleMap.cpp 810
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Objects’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. DebugMap.cpp 88
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Strategies’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 685
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 686
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 688
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 689
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 690
  • V1023 [CWE-460] O pointer fără proprietar este adăugat în containerul ‘Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 691
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 692
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 693
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Modifiers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. llvm-stress.cpp 694
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Operands’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Stash’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] Un pointer fără proprietar este adăugat în containerul ’Matchers’ prin metoda ’emplace_back’. Va apărea o scurgere de memorie în cazul unei excepții. GlobalISelEmitter.cpp 2702

Concluzie

În total, am înregistrat 60 de avertizări, după care m-am oprit. Există alte defecte pe care le găsește analizatorul PVS-Studio în LLVM? Da, există. Totuși, când am înregistrat fragmentele de cod pentru articol, era deja târziu, mai degrabă noapte, și am decis că e timpul să mă opresc.

Sper că v-a fost interesant și că veți dori să încercați analizatorul PVS-Studio.

Puteți descărca analizatorul și obține o cheie de activare la această pagină.

Cel mai important, utilizați analiza statică în mod regulat. Verificările ocazionale, efectuate de noi cu scopul de a populariza metodologia analizei statice și PVS-Studio nu reprezintă un scenariu normal.

Mult noroc în îmbunătățirea calității și fiabilității codului!

Găsirea erorilor în LLVM 8 cu ajutorul analizei PVS-Studio

Dacă doriți să împărtășiți acest articol cu o audiență vorbitoare de limba engleză, vă rog să folosiți linkul la traducere: Andrey Karpov. Găsirea bug-urilor în LLVM 8 cu PVS-Studio.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster