Gjejmë gabimet në LLVM 8 me ndihmën e analizatorit PVS-Studio

Gjejmë gabime në LLVM 8 me analizuesin PVS-Studio
Kanë kaluar më shumë se dy vjet që nga kontrolli i fundit i kodit të projektit LLVM me ndihmën e analizatorit tonë PVS-Studio. Le të sigurohemi që analizatori PVS-Studio vazhdon të jetë mjeti kryesor për identifikimin e gabimeve dhe dobësive të mundshme. Për këtë, do të kontrollojmë dhe gjejmë gabime të reja në lançimin LLVM 8.0.0.

Artikulli që duhet të shkruhet

Për të qenë i sinqertë, nuk doja të shkruaja këtë artikull. Nuk është e këndshme të shkruash për një projekt që ne e kemi kontrolluar disa herë (1, 2, 3). Më mirë do të shkruaja për diçka të re, por nuk kam zgjedhje.

Çdo herĂ« qĂ« del njĂ« version i ri LLVM ose njĂ« pĂ«rditĂ«sim Clang Static Analyzer, na vijnĂ« pyetje tĂ« tilla nĂ« email:

Shihni, versioni i ri i Clang Static Analyzer ka mĂ«suar tĂ« gjejĂ« gabime tĂ« reja! MĂ« duket se rĂ«ndĂ«sia e pĂ«rdorimit tĂ« PVS-Studio po zvogĂ«lohet. Clang gjen mĂ« shumĂ« gabime se mĂ« parĂ« dhe po arrin nĂ« mundĂ«sitĂ« e PVS-Studio. ÇfarĂ« mendoni pĂ«r kĂ«tĂ«?

Për këtë gjithmonë më pëlqen të përgjigjem diçka në frymën e:

Ne gjithashtu nuk qëndrojmë duarkryq! Ne kemi përmirësuar ndjeshëm mundësitë e analizatorit PVS-Studio. Prandaj, mos u shqetësoni, ne vazhdojmë të jemi liderë, si më parë.

Fatkeqe, ky Ă«shtĂ« njĂ« pĂ«rgjigje e keqe. Nuk ka prova. PikĂ«risht pĂ«r kĂ«tĂ« arsye po shkruaj kĂ«tĂ« artikull tani. Pra, projekti LLVM Ă«shtĂ« shqyrtuar pĂ«rsĂ«ri dhe janĂ« gjetur lloje tĂ« ndryshme gabimesh. Ato qĂ« mĂ« dukeshin interesante, do t’i tregoj tani. KĂ«to gabime nuk mund tĂ« gjenden nga Clang Static Analyzer (ose Ă«shtĂ« shumĂ« e vĂ«shtirĂ« tĂ« bĂ«het kĂ«shtu). Ne mund tĂ« gjejmĂ« ato. Madje, i kam gjetur dhe shkruar tĂ« gjitha kĂ«to gabime brenda njĂ« mbrĂ«mjeje.

NdĂ«rsa shkruarja e artikullit ka zgjatur disa javĂ«. Nuk mund tĂ« mĂ« bindja tĂ« gjitha kĂ«to t’i pĂ«rktheja nĂ« tekst :).

Për më tepër, nëse jeni të interesuar për teknologjitë që përdoren në analizuesin PVS-Studio për identifikimin e gabimeve dhe dobësive të mundshme, unë ofroj të njiheni me këtë shënim.

Diagnostikat e reja dhe të vjetra

Siç është theksuar, rreth dy vjet më parë projekti LLVM është kontrolluar përsëri, dhe gabimet e gjetura janë rregulluar. Tani në këtë artikull do të paraqiten një sasi e re gabimesh. Pse u gjetën gabime të reja? Ka 3 shkak për këtë:

  1. Projekti LLVM po zhvillohet, në të ndryshohet kodi i vjetër dhe shfaqet njëri i ri. Natyrisht, në kodin e ndryshuar dhe të shkruar shfaqen gabime të reja. Kjo tregon qartë se analiza statike duhet të aplikohet rregullisht, jo herë pas here. Artikujt tanë tregojnë mirë mundësitë e analizuesit PVS-Studio, por kjo nuk ka asnjë lidhje me përmirësimin e cilësisë së kodit dhe uljen e kostos së korrigjimit të gabimeve. Përdorni analizuesin statik të kodit rregullisht!
  2. Ne po përmirësojmë dhe zhvillojmë diagnostikat ekzistuese. Prandaj, analizuesi mund të zbulojë gabime që nuk janë vërejtur në kontrollet e mëparshme.
  3. Në PVS-Studio kanë dalë diagnostika të reja që nuk ishin dy vjet më parë. Vendosa t'i veçoj ato në një seksion të veçantë për të ilustruar zhvillimin e PVS-Studio.

Defektet e zbuluara nga diagnostikat që kanë ekzistuar dy vjet më parë

Fragma N1: Kopjimi-Pastaj

static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
  if (Name == "addcarryx.u32" || 
    ....
    Name == "avx512.mask.cvtps2pd.128" || 
    Name == "avx512.mask.cvtps2pd.256" || 
    Name == "avx512.cvtusi2sd" || 
    Name.startswith("avx512.mask.permvar.") || 
    Name.startswith("avx512.mask.permvar.") || 
    Name == "sse2.pmulu.dq" || 
    Name == "sse41.pmuldq" || 
    Name == "avx2.pmulu.dq" || 
  ....
}

Kujtesa PVS-Studio: V501 [CWE-570] Ekzistojnë shprehje të identike nën-shprehjesh 'Name.startswith("avx512.mask.permvar.")' në të majtë dhe të djathtë të operatorit '||'. AutoUpgrade.cpp 73

Dy herë kontrollohet që emri fillon me nënshkrimin "avx512.mask.permvar.". Në kontrollin e dytë, qartazi ishin dashur të shkruanin dicka tjetër, por harruan të rregullojnë tekstin e kopjuar.

Fragmenti N2: Gabim në shtyp

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

Kujtesa PVS-Studio: V501 Ekzistojnë shprehje të identike nën-shprehjesh 'CXNameRange_WantQualifier' në të majtë dhe të djathtë të operatorit '|'. CIndex.cpp 7245

Për shkak të një gabimi të shtypit, një konstant e emëruar përdoret dy herë CXNameRange_WantQualifier.

Fragmenti N3: Ngatërrim me privilegjet e operatorëve

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

Kujtesa PVS-Studio: V502 [CWE-783] Ndoshta operatori ‘?:’ funksionon ndryshe nga sa ishte pritur. Operatori ‘?:’ ka njĂ« prioritet mĂ« tĂ« ulĂ«t se operatori ‘==’. PPCTargetTransformInfo.cpp 404

Në mendimin tim, është një gabim shumë i bukur. Po, e di që kam përfytyrime të çuditshme për bukurinë :).

Aktualisht, sipas prioriteteve të operatorëve, shprehja llogaritet si në vijim:

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

Nga një pikëpamje praktike, një kusht i tillë nuk ka kuptim, pasi mund të shkurtuar në:

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

Ky është një gabim i qartë. Me siguri, 0/1 do të doja të krahasohej me një variabël Index. Për të korrigjuar kodin, është e nevojshme të shtoni kllapa rreth operatorit ternar:

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

Për ndryshe, operatori ternar është shumë i rrezikshëm dhe provokon gabime logjike. Bëni kujdes me të dhe mos jini të etur për të vendosur kllapat e rrethuar. Më shumë mbi këtë temë kam trajtuar këtu, në kapitullin "Kërceni operatorin ?: dhe e vendosni atë në kllapat e rrethuar".

Fragmenti N4, N5: Të dhënat null

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("nuk. mund të kthejë '") + LHS->getAsString() +
                    "' në varg");
    return nullptr;
  }
  ....
}

Kujtesa PVS-Studio: V522 [CWE-476] Mund të ndodhë dereferencimi i pikës së null 'LHS'. TGParser.cpp 2152

Nëse treguesi LHS është null, duhet të lëshohet një paralajmërim. Megjithatë, në vend të kësaj do të ndodhë dereferencimi i atij treguesi null: LHS->getAsString().

Kjo është një situatë mjaft tipike, ku gabimi fshihet në trajtuesin e gabimeve, pasi askush nuk e teston atë. Analizatorët statikë kontrollojnë të gjithë kodin e arritshëm, pavarësisht se sa shpesh përdoret ai. Ky është një shembull i shkëlqyer, si analiza statike plotëson metoda të tjera testimi dhe mbrojtjeje nga gabimet.

Një gabim i ngjashëm i trajtimit të treguesit RHS është bërë në kodin pak më poshtë: V522 [CWE-476] Mund të ndodhë dereferencimi i pikës së null 'RHS'. TGParser.cpp 2186

Fragmenti N6: Përdorimi i treguesit pas zhvendosjes

static Expected
ExtractBlocks(....)
{
  ....
  std::unique_ptr ProgClone = CloneModule(BD.getProgram(), VMap);
  ....
  BD.setNewProgram(std::move(ProgClone));                                // getFunction(MisCompFunctions[i].first);  // <=
    assert(NewF && "Function not found??");
    MiscompiledFunctions.push_back(NewF);
  }
  ....
}

Paralajmërim PVS-Studio: V522 [CWE-476] Dereferencimi i treguesit null 'ProgClone' mund të ndodhi. Miscompilation.cpp 601

Në fillim treguesi inteligjent ProgClone ndalon së qenuri pronar i objektit:

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

NĂ« tĂ« vĂ«rtetĂ«, tani ProgClone — Ă«shtĂ« njĂ« tregues null. Prandaj, pak mĂ« poshtĂ« duhet tĂ« ndodhĂ« dereferencimi i treguesit null:

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

Por, në të vërtetë, kjo nuk do ndodhë! Vëre se cikli në të vërtetë nuk ekzekutohet.

NĂ« fillim kontejneri MiscompiledFunctions pastrohet:

MiscompiledFunctions.clear();

Më pas, madhësia e këtij kontejneri përdoret në kushtin e ciklit:

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

ËshtĂ« e lehtĂ« tĂ« shikohet se cikli nuk nis. Mendoj se kjo Ă«shtĂ« gjithashtu njĂ« gabim, dhe kodi duhet tĂ« shkruhet ndryshe.

Duket se kemi takuar atë gabimin e njohur të çiftësisë! Një gabim maskon një tjetër :).

Fragmenti N7: Përdorimi i treguesit pas zhvendosjes

static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test,
                                    std::unique_ptr<Module> Safe) {
  outs() << "  Optimizimi i funksioneve në testim: ";
  std::unique_ptr<Module> Optimized =
      BD.runPassesOn(Test.get(), BD.getPassesToRun());
  if (!Optimized) {
    errs() << " Gabim gjatë ekzekutimit të kësaj sekuence kalimesh"
           << " në programin hyrës!n";
    BD.setNewProgram(std::move(Test));                       // <=
    BD.EmitProgressBitcode(*Test, "pass-error", false);      // <=
    if (Error E = BD.debugOptimizerCrash())
      return std::move(E);
    return false;
  }
  ....
}

Kujdes PVS-Studio: V522 [CWE-476] Mund tĂ« ndodhĂ« dereferencimi i treguesit null ‘Test’. Miscompilation.cpp 709

Sërish e njëjta situatë. Në fillim përmbajtja e objektit zhvendoset, pastaj ai përdoret si të mos kishte ndodhur asgjë. Po e has gjithnjë e më shpesh këtë situatë në kodin e programeve, pas hyrjes së semantikës së zhvendosjes në C++. Këtë e dua tek gjuha C++! Po shfaqen gjithnjë e më shumë mënyra për të goditur veten në këmbë. Analizatori PVS-Studio gjithmonë do të ketë punë :)).

Fragmenti N8: Tregues null

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

Kujdesi PVS-Studio: V522 [CWE-476] Mund tĂ« ndodhĂ« dereferenca e treguesit null ‘Type’. PrettyFunctionDumper.cpp 233

Përveç trajtuesve të gabimeve, zakonisht nuk testohen as funksionet e printimit të të dhënave për qëllime debugimi. Ky është një rast i tillë. Funksioni pret përdoruesin, i cili në vend që të zgjidhë problemet e tij, do të detyrohet ta rivendosë atë.

Saktë:

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

Fragmenti N9: Treguesi null

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

Kujdesi PVS-Studio: V522 [CWE-476] Mund tĂ« ndodhĂ« dereferenca e treguesit null ‘Ty’. SearchableTableEmitter.cpp 614

Mendoj se gjithçka është e qartë dhe nuk kërkon shpjegime.

Fragmenti N10: Gabim shtypi

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

Kujtesa PVS-Studio: V570 Variabli ‘Identifier->Type’ i Ă«shtĂ« caktuar vetĂ«. FormatTokenLexer.cpp 249

Nuk ka kuptim t'i caktohet vargu vetë. Më shumë mund të donit të shkruani:

Identifier->Type = Question->Type;

Fragmenti N11: Break i dyshimtë

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

Kujtesa PVS-Studio: V622 [CWE-478] Konsideroni tĂ« inspektoni deklaratĂ«n ‘switch’. ËshtĂ« e mundur qĂ« operatori i parĂ« ‘case’ mungon. SystemZAsmParser.cpp 652

Në fillim ka një operator shumë dyshues break. A e kishit harruar të shkruanit diçka tjetër këtu?

Fragmenti N12: Kontrollimi i treguesit pas dereferencimit

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

Kujtesa PVS-Studio: V595 [CWE-476] Treguesi ‘Callee’ u pĂ«rdor para se tĂ« verifikohej kundĂ«r nullptr. Kontrolloni linjat: 172, 174. AMDGPUInline.cpp 172

Tregues Callee në fillim dereferencohet në momentin e thirrjes së funksionit getTTI.

Dhe më pas del se ky tregues duhet të kontrollohet për barazi nullptr:

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

Por tashmë është vonë...

Fragmenti N13 — N
: Kontrolli i treguesit pas dereferencimit

Situata e përmendur në fragmentin e mëparshëm të kodit nuk është unike. Ajo ndodh këtu:

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

Kujdesi PVS-Studio: V595 [CWE-476] Pika ‘CalleeFn’ u pĂ«rdor para se tĂ« verifikohej kundĂ«r nullptr. Kontrolloni rreshtat: 1079, 1081. SimplifyLibCalls.cpp 1079

Dhe këtu:

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

Kujdesi PVS-Studio: V595 [CWE-476] Pika ‘ND’ u pĂ«rdor para se tĂ« verifikohej kundĂ«r nullptr. Kontrolloni rreshtat: 532, 534. SemaTemplateInstantiateDecl.cpp 532

Dhe këtu:

  • V595 [CWE-476] Pika ‘U’ u pĂ«rdor para se tĂ« verifikohej kundĂ«r nullptr. Kontrolloni rreshtat: 404, 407. DWARFFormValue.cpp 404
  • V595 [CWE-476] Pika ‘ND’ u pĂ«rdor para se tĂ« verifikohej kundĂ«r nullptr. Kontrolloni rreshtat: 2149, 2151. SemaTemplateInstantiate.cpp 2149

Më pas, nuk më interesoi të shqyrtoj paralajmërimet me numër V595. Prandaj, nuk e di nëse ka ndonjë gabim tjetër, përveç atyre të përmendur këtu. Ka të ngjarë që ka.

Fragma N17, N18: Shkëputje e dyshimtë

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

Kujtesa PVS-Studio: V629 [CWE-190] Konsideroni inspektimin e shprehjes ‘~(Size — 1) << 1’. ShkĂ«putja e vlerĂ«s 32-bit me njĂ« zgjerim tĂ« mĂ«passhĂ«m nĂ« tipin 64-bit. AArch64AddressingModes.h 260

Ndoshta, kjo nuk është një gabim, dhe kodi funksionon saktësisht siç ishte parashikuar. Por kjo është qartë një vend i dyshimtë, dhe duhet ta kontrolloni.

Supozoni se variabla Madhësia është 16, dhe atëherë autori i kodit planifikoi të merrte në variablën NImms vlerën:

1111111111111111111111111111111111111111111111111111111111100000

Megjithatë, në realitet, do të dalë vlera:

0000000000000000000000000000000011111111111111111111111111100000

Çështja Ă«shtĂ« se tĂ« gjitha llogaritjet bĂ«hen duke pĂ«rdorur tipin 32-bit tĂ« paadhet. Dhe vetĂ«m atĂ«herĂ«, ky tip 32-bit paadhet do tĂ« zgjerohet automatikisht nĂ« uint64_t. NĂ« kĂ«tĂ« rast, bitet mĂ« tĂ« larta do tĂ« jenĂ« zero.

Situatën mund ta rregulloni kështu:

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

SituatĂ« e ngjashme: V629 [CWE-190] Konsideroni inspektimin e shprehjes ‘Immr << 6’. ShkĂ«putja e vlerĂ«s 32-bit me njĂ« zgjerim tĂ« mĂ«passhĂ«m nĂ« tipin 64-bit. AArch64AddressingModes.h 269

Fragmenti N19: Ka është ky fjalë qenësore 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 përdor "vcc" token.
    // Anashkalo atë.
    continue;
  } if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {    // <=
    Op.addRegWithFPInputModsOperands(Inst, 2);
  } else if (Op.isDPPCtrl()) {
    Op.addImmOperands(Inst, 1);
  } else if (Op.isImm()) {
    // Përdorimi i argumenteve opsionale
    OptionalIdx[Op.getImmTy()] = I;
  } else {
    llvm_unreachable("Lloji i operandit është i pavlefshëm");
  }
  ....
}

Kujtesa PVS-Studio: V646 [CWE-670] Merrni parasysh tĂ« kontrolloni logjikĂ«n e aplikacionit. ËshtĂ« e mundur qĂ« fjalĂ« kyçe 'else' Ă«shtĂ« e humbur. AMDGPUAsmParser.cpp 5655

Nuk ka gabime këtu. Duke qenë se blloku then i parë nëse kthehet në continue, nuk ka rëndësi, a ka fjalë kyçe else apo jo. Në çdo rast, kodi do të funksionojë në të njëjtën mënyrë. Megjithatë, e humbura else e bën kodin më të pakonceptueshëm dhe të rrezikshëm. Nëse më vonë continue zhduket, atëherë kodi do të fillojë të funksionojë krejt ndryshe. Sipas mendimit tim, është më mirë të shtoni else.

Fragmenti N20: Katër gabime të ngjashme

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

Paralajmërimet e PVS-Studio:

  • V655 [CWE-480] Stringat u bashkuan por nuk janĂ« pĂ«rdorur. Konsideroni tĂ« kontrolloni shprehjen ‘Result + Name.str()’. Symbol.cpp 32
  • V655 [CWE-480] Stringat u bashkuan por nuk janĂ« pĂ«rdorur. Konsideroni tĂ« kontrolloni shprehjen ‘Result + «(ObjC Class) » + Name.str()’. Symbol.cpp 35
  • V655 [CWE-480] Stringat u bashkuan por nuk janĂ« pĂ«rdorur. Konsideroni tĂ« kontrolloni shprehjen ‘Result + «(ObjC Class EH) » + Name.str()’. Symbol.cpp 38
  • V655 [CWE-480] Stringat u bashkuan por nuk janĂ« pĂ«rdorur. Konsideroni tĂ« kontrolloni shprehjen ‘Result + «(ObjC IVar) » + Name.str()’. Symbol.cpp 41

Rastësisht, operatori += përdoret në vend të operatorit +. Si rezultat, konstruktet krijohen pa kuptim.

Fragmenti N21: Sjellje e paqartë

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 cannot be empty");

    for (auto &Op : Ops) {
      assert(!Op.empty() && "Empty operator");
      if (FeaturesMap.find(Op) == FeaturesMap.end())
        FeaturesMap[Op] = FeaturesMap.size();
    }
  }
}

Provoni të gjejë kodin e rrezikshëm vetë. Ky është një imazh për të shpërqëndruar vëmendjen, në mënyrë që të mos shihni menjëherë përgjigjen:

Gjejmë gabime në LLVM 8 me analizuesin PVS-Studio

Kujtesa PVS-Studio: V708 [CWE-758] PĂ«rdoret ndĂ«rtim i rrezikshĂ«m: ‘FeaturesMap[Op] = FeaturesMap.size()’, ku ‘FeaturesMap’ Ă«shtĂ« klasĂ« ‘map’. Kjo mund tĂ« çojĂ« nĂ« sjellje tĂ« paqartĂ«. RISCVCompressInstEmitter.cpp 490

Vija problematike:

FeaturesMap[Op] = FeaturesMap.size();

Nëse elementi Op nuk gjendet, krijohet një element i ri në hartë dhe atje shkruhet numri i elementeve në këtë hartë. Por nuk dihet se a do të thirret funksioni size para apo pas shtimit të elementit të ri.

Fragmenti N22-N24: Rrëfimi i përsëritur

Error MachOObjectFile::checkSymbolTable() const {
  ....
  } else {
    MachO::nlist STE = getSymbolTableEntry(SymDRI);
    NType = STE.n_type;                              // <=
    NType = STE.n_type;                              // <=
    NSect = STE.n_sect;
    NDesc = STE.n_desc;
    NStrx = STE.n_strx;
    NValue = STE.n_value;
  }
  ....
}

Kujtesa PVS-Studio: V519 [CWE-563] Variabli ‘NType’ i Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Ndoshta kjo Ă«shtĂ« njĂ« gabim. Kontrolloni rreshtat: 1663, 1664. MachOObjectFile.cpp 1664

Mendoj se nuk ka ndonjë gabim të vërtetë këtu. Thjesht është një caktim i panevojshëm i përsëritur. Megjithatë, është një gabim.

Njësoj:

  • V519 [CWE-563] Variabli ‘B.NDesc’ i Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Ndoshta kjo Ă«shtĂ« njĂ« gabim. Kontrolloni rreshtat: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Variabli i Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Ndoshta kjo Ă«shtĂ« njĂ« gabim. Kontrolloni rreshtat: 59, 61. coff2yaml.cpp 61

Fragmenti N25-N27: Përsëritje caktimesh

Tani le të shqyrtojmë pak një variant tjetër të caktimit të përsëritur.

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

ParalajmĂ«rim PVS-Studio: V519 [CWE-563] Variabli ‘Alignment’ i Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Ndoshta kjo Ă«shtĂ« njĂ« gabim. Kontrolloni rreshtat: 1158, 1160. LoadStoreVectorizer.cpp 1160

Ky është një kod shumë të çuditshëm, i cili duket se përmban një gabim logjik. Në fillim, variabelja Alignment i caktohet një vlerë në varësi të kushteve. Dhe pastaj ndodh përsëri caktimi, por tani pa asnjë kontroll.

Situatat e ngjashme mund t'i shihni këtu:

  • V519 [CWE-563] NjĂ« variabĂ«l ‘Effects’ Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Mund tĂ« jetĂ« njĂ« gabim. Kontrolloni rreshtat: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] NjĂ« variabĂ«l ‘ExpectNoDerefChunk’ Ă«shtĂ« caktuar vlera dy herĂ« radhazi. Mund tĂ« jetĂ« njĂ« gabim. Kontrolloni rreshtat: 4970, 4973. SemaType.cpp 4973

Fragmenti N28: Një kusht gjithmonë i vërtetë

static int readPrefixes(struct InternalInstruction* insn) {
  ....
  uint8_t byte = 0;
  uint8_t nextByte;
  ....
  if (byte == 0xf3 && (nextByte == 0x88 || nextByte == 0x89 ||
                       nextByte == 0xc6 || nextByte == 0xc7)) {
    insn->xAcquireRelease = true;
    if (nextByte != 0x90) // PAUSE instruction support             // <=
      break;
  }
  ....
}

Kujtesa PVS-Studio: V547 [CWE-571] Shprehja ‘nextByte != 0x90’ Ă«shtĂ« gjithmonĂ« e vĂ«rtetĂ«. X86DisassemblerDecoder.cpp 379

Kontrolli nuk ka sens. Variabël nextByte nuk është kurrë e barabartë me vlerën 0x90, që rrjedh nga kontrolli i mëparshëm. Kjo është një gabim logjik.

Fragmenti N29 — N
: Kushtet gjithmonĂ« tĂ« vĂ«rteta/falce

Analizuesi jep shumë paralajmërime për atë se e gjithë kushti (V547) ose një pjesë e tij (V560) është gjithmonë e vërtetë ose e gabuar. Shpesh, këto nuk janë vërtet gabime, por thjesht kod i papërshtatshëm, rezultat i shpërndarjes së makros dhe gjëra të tjera të ngjashme. Megjithatë, ka kuptim të shikoni të gjitha këto paralajmërime, pasi herë pas here hasen gabime të vërteta logjike. Për shembull, ky fragment kodi është i dyshimtë:

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

Kujtesa PVS-Studio: V560 [CWE-570] Një pjesë e shprehjes kushtore është gjithmonë e gabuar: RegNo == 0xe. ARMDisassembler.cpp 939

Konstanta 0xE është vlera 14 në sistemin dekimal. Kontrolli RegNo == 0xe nuk ka kuptim, pasi nëse RegNo > 13, atëherë funksioni do të përfundojë.

Ishin shumë paralajmërime të tjera me identifikuesin V547 dhe V560, por, siç ndodhi me V595, të shqyrtoja këto paralajmërime nuk më interesonte. Ishte e qartë se kisha mjaft material për të shkruar një artikull :). Prandaj, është e paqartë se sa gabime të tilla mund të identifikohen në LLVM me PVS-Studio.

Ja do një shembull se pse është e mërzitshme të studiohen këto paralajmërime. Analizatori ka të drejtë kur jep një paralajmërim për këtë kod. Por kjo nuk është një gabim.

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

ParalajmĂ«rimi PVS-Studio: V547 [CWE-570] Shprehja ‘!HasError’ Ă«shtĂ« gjithmonĂ« false. UnwrappedLineParser.cpp 1635

Fragmenti N30: Return i dyshimtë

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

Kujtesa PVS-Studio: V612 [CWE-670] NjĂ« ‘return’ i pandĂ«rprerĂ« brenda njĂ« cikli. R600OptimizeVectorRegisters.cpp 63

Kjo është ose një gabim, ose një teknikë specifike që ka për qëllim të shpjegojë diçka për programuesit që lexojnë kodin. Për mua, kjo strukturë nuk shpjegon asgjë dhe duket shumë e dyshimtë. Më mirë të mos shkruhet kështu :).

E lodhur? Atëherë është koha për të bërë çaj ose kafe.

Gjejmë gabime në LLVM 8 me analizuesin PVS-Studio

Defekte të zbuluara nga diagnostikat e reja

Mendoj se 30 ndodhi të diagnostikave të vjetra është e mjaftueshme. Le të shohim tani se çfarë interesante mund të gjejmë nga diagnostikat e reja që janë shtuar në analizator pas të kaluarën kontrollimeve. Gjatë kësaj kohe, analizatori C++ ka shtuar 66 diagnostika të përgjithshme.

Fragmenti N31: Kod i panavikshëm

Error CtorDtorRunner::run() {
  ....
  if (auto CtorDtorMap =
          ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names),
                    NoDependenciesToRegister, true))
  {
    ....
    return Error::success();
  } else
    return CtorDtorMap.takeError();

  CtorDtorsByPriority.clear();

  return Error::success();
}

Kujtesa PVS-Studio: V779 [CWE-561] Kod i panavikshëm i zbuluar. Mund të jetë e pranishme një gabim. ExecutionUtils.cpp 146

Siç e shihni, të dy degët e operatorit nëse përfundojnë me thirrjen e operatorit kthehu. Përkatësisht, kontejneri CtorDtorsByPriority kurrë nuk do të pastrohet.

Fragmenti N32: Kod i panavikshëm

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(), "përshtatë e papritur e përmbledhjes");
  }
  Lex.setIgnoreColonInIdentifiers(false);                      // <=
  return false;
}

Kujdesi PVS-Studio: V779 [CWE-561] Kod i panavikshëm i zbuluar. Mund të jetë e pranishme një gabim. LLParser.cpp 835

Një situatë interesante. Le të shikojmë fillimisht këtë vend:

return ParseTypeIdEntry(SummaryID);
break;

Në shikim të parë duket se nuk ka gabim këtu. Duket se operator break këtu është e tepërt, dhe mund të hiqet thjesht. Sidoqoftë, nuk është kaq e lehtë.

Analizatori jep paralajmërim në linjat:

Lex.setIgnoreColonInIdentifiers(false);
return false;

Dhe në të vërtetë, ky kod është i papërballueshëm. Të gjitha rastet në switch përfundojnë me thirrjen operatori kthehu. Dhe tani, e vetme, pa kuptim break nuk duket kaq inekzistente! Ndoshta një nga degët duhet të përfundojë me break, e jo me kthehu?

Fragment N33: Zeroimi random i bitëve të lartë

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

Kujtesa PVS-Studio: V784 Madhësia e maskës së bitëve është më e vogël se madhësia e operandit të parë. Kjo do të shkaktojë humbjen e bitëve të lartë. RuntimeDyld.cpp 815

Vini re se funksioni getStubAlignment kthen tipin unsigned. Le t'i llogarisim vlerat e shprehjes, duke supozuar se funksioni kthen vlerën 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFF8‬u

Tani vini re se variabla DataSize ka 64-biti e paznjohur. KĂ«shtu, kur kryhet operacioni DataSize & 0xFFFFFFF8‬u, tĂ« tridhjetĂ« e dy bitĂ«t mĂ« tĂ« lartĂ« do tĂ« jenĂ« zero. Probabiliteti Ă«shtĂ« se kjo nuk Ă«shtĂ« ajo qĂ« programuesi donte. Dyshoj se ai dĂ«shiron tĂ« llogaritĂ«: DataSize & 0xFFFFFFFFFFFFFFF8‬u.

Për të rregulluar gabimin, duhet të shkruhet kështu:

DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);

Ose kështu:

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

Fragmenti N34: Shndërrimi i tipit të dështuar

template <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
                      SmallVectorImpl<T> &ScaledMask) {
  assert(0 < Scale && "Faktori i papritur i shkallës");
  int NumElts = Mask.size();
  ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
  ....
}

Kujtesa PVS-Studio: V1028 [CWE-190] MundĂ«si overflow. Konsideroni tĂ« ktheni operandĂ«t e operatorit ‘NumElts * Scale’ nĂ« tipin ‘size_t’, jo rezultatin. X86ISelLowering.h 1577

Shndërrimi i qartë të tipit përdoret për të shmangur overflow gjatë shumëzimit të variablave të tipit int. Megjithatë, këtu shndërrimi i qartë të tipit nuk mbron nga overflow. Në fillim variablat do të shumëzohen, dhe vetëm pastaj rezultati 32-bit do të zgjerohet në tipin size_t.

Fragmenti N35: Copy-Paste i dështuar

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] U gjetĂ«n dy fragmente tĂ« ngjashme kodi. Ndoshta, kjo Ă«shtĂ« njĂ« gabim dhe variabla ‘Op1’ duhet tĂ« pĂ«rdoret nĂ« vend tĂ« ‘Op0’. InstCombineCompares.cpp 5507

Ky diagnostikim i ri interesant identifikon situatat kur një fragment kodi është kopjuar dhe disa emra janë ndryshuar, por një vend nuk është përmirësuar.

Kujtoni se në bllokun e dytë u ndryshua Op0 në Op1. Por në një vend nuk është përmirësuar. Me sa duket, duhet të ishte shkruar kështu:

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

Fragmenti N36: Çrregullimi nĂ« variabla

struct Status {
  unsigned Mask;
  unsigned Mode;

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

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

Kujtesa PVS-Studio: V1001 [CWE-563] Variabla ‘Mode’ caktohet, por nuk pĂ«rdoret deri nĂ« fund tĂ« funksionit. SIModeRegister.cpp 48

ÇohĂ« e rrezikshme Ă«shtĂ« t’u jepni argumenteve tĂ« funksionit tĂ« njĂ«jtin emĂ«r si anĂ«tarĂ«ve tĂ« klasĂ«s. ËshtĂ« shumĂ« e lehtĂ« pĂ«r t'u ngatĂ«rruar. Ky Ă«shtĂ« njĂ« rast.

Ky shprehje nuk ka kuptim:

Argumenti i funksionit ndryshon. Dhe kaq. Ky argument nuk përdoret në asnjë mënyrë tjetër. Ndoshta duhej të ishte shkruar kështu:

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

Fragmenti N37: Ngatërrimi në variabla

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

ParalajmĂ«rim PVS-Studio: V1001 [CWE-563] Variabli ‘Size’ caktohet por nuk pĂ«rdoret deri nĂ« fund tĂ« funksionit. Object.cpp 424

Situata është e ngjashme me të mëparshmen. Duhet të ishte shkruar:

this->Size += this->EntrySize;

Fragmenti N38-N47: Përshtatësin e harroi të kontrollohej

Më parë ne shqyrtuam shembuj të aktivizimit të diagnostikës V595. Thelbi i saj është se përshtatësi zhbllokohet në fillim, dhe pastaj kontrollohet. Diagnostika e re V1004 është e kundërt me të dhe gjen shumë gabime. Ajo zbulohet situata kur treguesi u kontrollua në fillim, dhe pastaj u harrua. Le të shqyrtojmë këto raste të gjetura brenda LLVM.

int getGEPCost(Type *PointeeType, const Value *Ptr,
               ArrayRef<const Value *> Operands) {
  ....
  if (Ptr != nullptr) {                                            // <=
    assert(....);
    BaseGV = dyn_cast<GlobalValue>(Ptr->stripPointerCasts());
  }
  bool HasBaseReg = (BaseGV == nullptr);

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

ParalajmĂ«rimi PVS-Studio: V1004 [CWE-476] Treguesi ‘Ptr’ u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pasi u verifikua ndaj nullptr. Kontrolloni rreshtat: 729, 738. TargetTransformInfoImpl.h 738

Variabla Ptr mund të jetë e barabartë me nullptr, e cila është e dëshmuar nga verifikimi:

if (Ptr != nullptr)

Megjithatë, më poshtë ky tregues përdoret tashmë pa një verifikim paraprak:

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

Le të shqyrtojmë një rast tjetër të ngjashëm.

llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
                                                          bool Stub) {
  ....
  auto *FD = dyn_cast<FunctionDecl>(GD.getDecl());
  SmallVector<QualType, 16> ArgTypes;
  if (FD)                                                                // <=
    for (const ParmVarDecl *Parm : FD->parameters())
      ArgTypes.push_back(Parm->getType());
  CallingConv CC = FD->getType()->castAs<FunctionType>()->getCallConv(); // <=
  ....
}

Paralajmërim PVS-Studio: V1004 [CWE-476] Pika 'FD' u përdor në mënyrë të pasigurt pas verifikimit të saj ndaj nullptr. Kontrolloni rreshtat: 3228, 3231. CGDebugInfo.cpp 3231

Kujdesi për treguesin FD. Jam i sigurt se problemi duket qartë dhe nuk kërkohet shpjegim të veçantë.

Dhe gjithashtu:

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

Paralajmërim PVS-Studio: V1004 [CWE-476] Pika 'PtrTy' u përdor në mënyrë të pasigurt pas verifikimit të saj ndaj nullptr. Kontrolloni rreshtat: 960, 965. InterleavedLoadCombinePass.cpp 965

Si të mbroheni nga këto gabime? Bëni kujdes në Code-Review dhe përdorni analizatorin statik PVS-Studio për kontrolla të rregullta të kodit.

Nuk ka kuptim të paraqiten fragmente të tjera të kodit me gabime të këtij lloji. Do të lë në artikull vetëm listën e paralajmërimeve:

  • V1004 [CWE-476] Pika 'Expr' u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit tĂ« saj ndaj nullptr. Kontrolloni rreshtat: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] Pika 'PI' u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit tĂ« saj ndaj nullptr. Kontrolloni rreshtat: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] Pika 'StatepointCall' u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit tĂ« saj ndaj nullptr. Kontrolloni rreshtat: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] Pika ‘RV’ u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit ndaj nullptr. Kontrolloni linjat: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] Pika ‘CalleeFn’ u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit ndaj nullptr. Kontrolloni linjat: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] Pika ‘TC’ u pĂ«rdor nĂ« mĂ«nyrĂ« tĂ« pasigurt pas verifikimit ndaj nullptr. Kontrolloni linjat: 1819, 1824. Driver.cpp 1824

Fragmenti N48-N60: Nuk është kritik, por ka një defekt (mundësia e një rrjedhje memorjeje)

std::unique_ptr<IRMutator> createISelMutator() {
  ....
  std::vector<std::unique_ptr<IRMutationStrategy>> Strategjitë;
  Strategjitë.emplace_back(
      new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
  ....
}

Kujtesa PVS-Studio: V1023 [CWE-460] NjĂ« pointer pa pronar Ă«shtĂ« shtuar nĂ« kontejnerin ‘Strategjitë’ nga metodi ’emplace_back’. NjĂ« rrjedhje memorjeje do tĂ« ndodhi nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-isel-fuzzer.cpp 58

Për të shtuar një element në fund të një kontejneri të tipit std::vector<std::unique_ptr<X>> nuk mund të shkruani thjesht xxx.push_back(new X), sepse nuk ka një transformim të qartë nga X* në std::unique_ptr<X>.

Një zgjidhje e zakonshme është të shkruani xxx.emplace_back(new X), sepse kompilon: metoda emplace_back konstruktori elementin direkt nga argumentet dhe për këtë arsye mund të përdorë konstruktorë të qartë.

Kjo nuk është e sigurt. Nëse vektori është i plotë, ndodh një ri-alokim memorjeje. Operacioni i ri-alokimit të memorjes mund të dështojë, duke shkaktuar gjenerimin e një përjashtimi std::bad_alloc. Në këtë rast, treguesi do të humbasë, dhe objekti i krijuar kurrë nuk do të fshihet.

Zgjidhja më e sigurt është krijimi i unique_ptr, i cili do të zotërojë treguesin deri në momentin që vektori do të përpiqet të rishpërndajë memorien:

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

Duke filluar nga C++14, mund të përdorni 'std::make_unique':

xxx.push_back(std::make_unique())

Ky lloj defekti nuk është kritik për LLVM. Nëse nuk është e mundur të alokoni memori, funksionimi i kompajlerit do të ndalet thjesht. Megjithatë, për aplikacionet me kohë të gjatë uptime, të cilat nuk mund të përfundojnë thjesht nëse dështoni në alokimin e memories, kjo mund të jetë një gabim i vërtetë i pakëndshëm.

Pra, ndonëse ky kod nuk paraqet rrezik praktik për LLVM, e kam konsideruar të dobishme të flas për këtë model gabimi dhe se si analizatori PVS-Studio ka mësuar ta identifikojë.

Këto janë paralajmërime të tjera të këtij tipi:

  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« konteinerin ‘Passes’ nga metoda ’emplace_back’. NjĂ« rrjedhje memorije do tĂ« ndodhĂ« nĂ« rastin e njĂ« pĂ«rjashtimi. PassManager.h 546
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« konteinerin ‘AAs’ nga metoda ’emplace_back’. NjĂ« rrjedhje memorije do tĂ« ndodhĂ« nĂ« rastin e njĂ« pĂ«rjashtimi. AliasAnalysis.h 324
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Entries' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'AllEdges' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. CFGMST.h 268
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'VMaps' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Records' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. FDRLogBuilder.h 30
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'PendingSubmodules' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. ModuleMap.cpp 810
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Objects' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. DebugMap.cpp 88
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Strategies' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Modifiers' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 685
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Modifiers' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 686
  • V1023 [CWE-460] NjĂ« tregues pa pronar Ă«shtĂ« shtuar nĂ« enĂ«n 'Modifiers' nga metoda 'emplace_back'. NjĂ« rrjedhje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 688
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 689
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 690
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 691
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 692
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 693
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Modifiers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. llvm-stress.cpp 694
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Operands’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Stash’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] NjĂ« tregues pa pronar shtohet nĂ« kontejnerin ‘Matchers’ nga metoda ’emplace_back’. NjĂ« humbje memorie do tĂ« ndodhĂ« nĂ« rast tĂ« njĂ« pĂ«rjashtimi. GlobalISelEmitter.cpp 2702

Përfundimi

Derisa kam shkruar gjithsej 60 paralajmërime, ndaluam. A ka defekte të tjera që analisti PVS-Studio zbulon në LLVM? Po, ka. Megjithatë, kur po shkruaja fragmente kodi për artikullin, erdhi vonë në mbrëmje, më saktësisht natën, dhe vendosa se ishte koha të ndaloja.

Shpresoj se ishit të interesuar dhe do të donit ta provonit analizuesin PVS-Studio.

Mund ta shkarkoni analizuesin dhe të merrni një çelës provues në këtë faqe.

E rëndësishme, përdorni analizën statike rregullisht. Kontrollime të veçanta, të cilat ne i kryejmë me qëllim popullarizimin e metodologjisë së analizës statike dhe PVS-Studio nuk janë një skenar normal.

Suksese në përmirësimin e cilësisë dhe besueshmërisë së kodit!

Gjejmë gabime në LLVM 8 me analizuesin PVS-Studio

Nëse dëshironi ta ndani këtë artikull me publikun anglishtfolës, ju lutem përdorni lidhjen në përkthim: Andrey Karpov. Gjetja e Gabimeve në LLVM 8 me PVS-Studio.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster