LLVM 8-ում սխալների հայտնաբերում PVS-Studio անալիզատորի միջոցով

LLVM 8-ում սխալների հայտնաբերում PVS-Studio անալիզատորի միջոցով
Ավելի քան երկու տարի է անցել LLVM նախագծի վերջին ծածկագրի ստուգումից՝ օգտագործելով մեր PVS-Studio անալիզատորը: Եկեք համոզվենք, որ PVS-Studio անալիզատորը դեռևս առաջատար գործիք է սխալների և հնարավոր խոցելիության հայտնաբերման համար: Դա անելու համար մենք կստուգենք և կգտնենք նոր սխալներ LLVM 8.0.0 թողարկման մեջ:

Հոդվածը պետք է գրվի

Ճիշտն ասած, ես չէի ուզում գրել այս հոդվածը: Հետաքրքիր չէ գրել մի նախագծի մասին, որը մենք արդեն մի քանի անգամ ստուգել ենք (1, 2, 3) Ավելի լավ է նոր բանի մասին գրել, բայց ես այլընտրանք չունեմ.

Ամեն անգամ, երբ թողարկվում կամ թարմացվում է LLVM-ի նոր տարբերակը Clang ստատիկ անալիզատոր, մեր փոստով ստանում ենք հետևյալ տիպի հարցեր.

Տեսեք, Clang Static Analyzer-ի նոր տարբերակը սովորել է գտնել նոր սխալներ: Ինձ թվում է, որ PVS-Studio-ի օգտագործման արդիականությունը նվազում է։ Clang-ը գտնում է ավելի շատ սխալներ, քան նախկինում և հասնում է PVS-Studio-ի հնարավորություններին: Ի՞նչ կարծիքի եք այս մասին։

Սրան ես միշտ ուզում եմ պատասխանել նման բան.

Մենք էլ պարապ չենք նստում։ Մենք զգալիորեն բարելավել ենք PVS-Studio անալիզատորի հնարավորությունները: Այնպես որ, մի անհանգստացեք, մենք նախկինի պես շարունակում ենք առաջատար լինել։

Ցավոք սրտի, սա վատ պատասխան է։ Դրանում ապացույցներ չկան։ Եվ դրա համար ես հիմա գրում եմ այս հոդվածը։ Այսպիսով, LLVM նախագիծը ևս մեկ անգամ ստուգվել է և նրանում հայտնաբերվել են մի շարք սխալներ։ Ես հիմա ցույց կտամ նրանց, որոնք ինձ հետաքրքիր էին թվում: Clang Static Analyzer-ը չի կարող գտնել այս սխալները (կամ չափազանց անհարմար է դա անել իր օգնությամբ): Բայց մենք կարող ենք։ Ավելին, ես գտա և գրի առա այս բոլոր սխալները մեկ երեկոյան։

Բայց հոդվածը գրելը տևեց մի քանի շաբաթ: Ես պարզապես չկարողացա ինձ ստիպել այս ամենը տեքստի մեջ դնել :):

Ի դեպ, եթե ձեզ հետաքրքրում է, թե ինչ տեխնոլոգիաներ են օգտագործվում PVS-Studio անալիզատորում սխալներն ու հնարավոր խոցելիությունները բացահայտելու համար, ապա առաջարկում եմ ծանոթանալ սրա հետ. Նշում.

Նոր և հին ախտորոշում

Ինչպես արդեն նշվեց, մոտ երկու տարի առաջ LLVM նախագիծը հերթական անգամ ստուգվեց, և հայտնաբերված սխալները ուղղվեցին: Այժմ այս հոդվածը կներկայացնի սխալների նոր խմբաքանակ: Ինչու են հայտնաբերվել նոր սխալներ: Դրա համար կա 3 պատճառ.

  1. LLVM նախագիծը զարգանում է՝ փոխելով հին ծածկագիրը և ավելացնելով նոր ծածկագիրը: Բնականաբար, փոփոխված և գրված կոդում նոր սխալներ կան։ Սա հստակ ցույց է տալիս, որ ստատիկ վերլուծությունը պետք է պարբերաբար օգտագործվի, և ոչ երբեմն: Մեր հոդվածները լավ ցույց են տալիս PVS-Studio անալիզատորի հնարավորությունները, բայց դա ոչ մի կապ չունի կոդի որակի բարելավման և սխալների շտկման ծախսերի նվազեցման հետ: Պարբերաբար օգտագործեք ստատիկ կոդի անալիզատոր:
  2. Մենք վերջնական տեսքի ենք բերում և բարելավում առկա ախտորոշումը: Հետևաբար, անալիզատորը կարող է բացահայտել այն սխալները, որոնք նա չի նկատել նախորդ սկանավորման ժամանակ:
  3. PVS-Studio-ում հայտնվել են նոր դիագնոստիկա, որոնք գոյություն չունեին 2 տարի առաջ։ Ես որոշեցի առանձնացնել դրանք առանձին բաժնում՝ հստակ ցույց տալու PVS-Studio-ի զարգացումը:

2 տարի առաջ առկա ախտորոշմամբ հայտնաբերված թերությունները

Հատված N1՝ Copy-Paste

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

PVS-Studio նախազգուշացում. V501 [CWE-570] Կան նույնական ենթաարտահայտություններ «Name.startswith(«avx512.mask.permvar.»)» «||»-ի ձախ և աջ կողմում: օպերատոր. AutoUpgrade.cpp 73

Կրկնակի ստուգվում է, որ անունը սկսվում է «avx512.mask.permvar» ենթատողով: Երկրորդ ստուգման ժամանակ ակնհայտորեն ուզում էին այլ բան գրել, բայց մոռացել էին ուղղել պատճենված տեքստը։

Հատված N2. Տառասխալ

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. V501 «|»-ի ձախ և աջ «CXNameRange_WantQualifier» ենթաարտահայտությունները նույնական են: օպերատոր. CIndex.cpp 7245

Տառասխալի պատճառով նույն անունով հաստատունն օգտագործվում է երկու անգամ CXNameRange_WantQualifier.

Հատված N3. շփոթություն օպերատորի գերակայության հետ

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

PVS-Studio նախազգուշացում. V502 [CWE-783] Հավանաբար «?:» օպերատորն աշխատում է այլ կերպ, քան սպասվում էր: «?:» օպերատորն ավելի ցածր առաջնահերթություն ունի, քան «==» օպերատորը: PPCTargetTransformInfo.cpp 404

Իմ կարծիքով, սա շատ գեղեցիկ սխալ է։ Այո, ես գիտեմ, որ տարօրինակ պատկերացումներ ունեմ գեղեցկության մասին :):

Այժմ, ըստ օպերատորի առաջնահերթությունները, արտահայտությունը գնահատվում է հետևյալ կերպ.

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

Գործնական տեսանկյունից նման պայմանը իմաստ չունի, քանի որ այն կարող է կրճատվել.

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

Սա ակնհայտ սխալ է։ Ամենայն հավանականությամբ, նրանք ցանկացել են համեմատել 0/1-ը փոփոխականի հետ ինդեքս. Կոդը շտկելու համար անհրաժեշտ է եռակի օպերատորի շուրջ փակագծեր ավելացնել.

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

Ի դեպ, եռակի օպերատորը շատ վտանգավոր է և տրամաբանական սխալներ է առաջացնում։ Շատ զգույշ եղեք դրա հետ և մի ագահ մի եղեք փակագծերով։ Ես ավելի մանրամասն նայեցի այս թեմային այստեղ, «Զգուշացեք ?. օպերատորից և փակեք այն փակագծերում» գլխում։

Հատված N4, N5. Զուր ցուցիչ

Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
  ....
  TypedInit *LHS = dyn_cast<TypedInit>(Result);
  ....
  LHS = dyn_cast<TypedInit>(
    UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get())
      ->Fold(CurRec));
  if (!LHS) {
    Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() +
                    "' to string");
    return nullptr;
  }
  ....
}

PVS-Studio նախազգուշացում. V522 [CWE-476] «LHS» զրոյական ցուցիչի անջատումը կարող է տեղի ունենալ: TGParser.cpp 2152

Եթե ​​ցուցիչը LHS զրոյական է, նախազգուշացում պետք է արվի. Այնուամենայնիվ, փոխարենը, այս նույն զրոյական ցուցիչը կհեռացվի՝ LHS->getAsString().

Սա շատ բնորոշ իրավիճակ է, երբ սխալը թաքնված է սխալ մշակողի մեջ, քանի որ ոչ ոք չի ստուգում դրանք: Ստատիկ անալիզատորները ստուգում են բոլոր հասանելի ծածկագրերը, անկախ նրանից, թե որքան հաճախ է այն օգտագործվում: Սա շատ լավ օրինակ է, թե ինչպես է ստատիկ վերլուծությունը լրացնում փորձարկման և սխալներից պաշտպանվելու այլ մեթոդներ:

Նմանատիպ ցուցիչի հետ աշխատելու սխալ RHS թույլատրվում է հենց ներքևում գտնվող ծածկագրում. V522 [CWE-476] «RHS» զրոյական ցուցիչի անջատումը կարող է տեղի ունենալ: TGParser.cpp 2186

Հատված N6. ցուցիչի օգտագործումը շարժվելուց հետո

static Expected<bool>
ExtractBlocks(....)
{
  ....
  std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap);
  ....
  BD.setNewProgram(std::move(ProgClone));                                // <=
  MiscompiledFunctions.clear();

  for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
    Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);  // <=
    assert(NewF && "Function not found??");
    MiscompiledFunctions.push_back(NewF);
  }
  ....
}

PVS-Studio Զգուշացում. V522 [CWE-476] «ProgClone» զրոյական ցուցիչի անջատում կարող է տեղի ունենալ: Սխալ կազմավորում.cpp 601

Սկզբում խելացի ցուցիչ ProgClone դադարում է տիրապետել օբյեկտին.

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

Փաստորեն, հիմա ProgClone զրոյական ցուցիչ է: Հետևաբար, զրոյական ցուցիչի հղումը պետք է տեղի ունենա հենց ստորև.

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

Բայց իրականում դա տեղի չի ունենա։ Նկատի ունեցեք, որ օղակն իրականում չի կատարվում:

Տարայի սկզբում Սխալ կազմված գործառույթներ մաքրված:

MiscompiledFunctions.clear();

Հաջորդը, այս կոնտեյների չափը օգտագործվում է հանգույցի վիճակում.

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

Հեշտ է տեսնել, որ հանգույցը չի սկսվում: Կարծում եմ, որ սա նույնպես սխալ է, և կոդը պետք է այլ կերպ գրվի:

Թվում է, թե մենք բախվել ենք սխալների այդ հայտնի հավասարությանը։ Մի սխալը քողարկում է մյուսը :):

Հատված N7. ցուցիչի օգտագործումը շարժվելուց հետո

static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test,
                                    std::unique_ptr<Module> Safe) {
  outs() << "  Optimizing functions being tested: ";
  std::unique_ptr<Module> Optimized =
      BD.runPassesOn(Test.get(), BD.getPassesToRun());
  if (!Optimized) {
    errs() << " Error running this sequence of passes"
           << " on the input program!n";
    BD.setNewProgram(std::move(Test));                       // <=
    BD.EmitProgressBitcode(*Test, "pass-error", false);      // <=
    if (Error E = BD.debugOptimizerCrash())
      return std::move(E);
    return false;
  }
  ....
}

PVS-Studio-ի նախազգուշացում. V522 [CWE-476] «Թեստ» զրոյական ցուցիչի անջատում կարող է տեղի ունենալ: Սխալ կազմավորում.cpp 709

Կրկին նույն իրավիճակը. Սկզբում օբյեկտի պարունակությունը տեղափոխվում է, իսկ հետո այն օգտագործվում է այնպես, կարծես ոչինչ չի եղել։ Ես այս իրավիճակը ավելի ու ավելի հաճախ եմ տեսնում ծրագրային կոդում այն ​​բանից հետո, երբ շարժման իմաստաբանությունը հայտնվեց C++-ում: Ահա թե ինչու ես սիրում եմ C++ լեզուն: Գոյություն ունեն ավելի ու ավելի շատ նոր եղանակներ՝ սեփական ոտքը կրակելու համար: PVS-Studio անալիզատորը միշտ գործ կունենա :):

Հատված N8. Զուր ցուցիչ

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

PVS-Studio-ի նախազգուշացում. V522 [CWE-476] «Տիպ» զրոյական ցուցիչի անջատում կարող է տեղի ունենալ: PrettyFunctionDumper.cpp 233

Սխալների մշակիչներից բացի, վրիպազերծման տպման գործառույթները սովորաբար չեն փորձարկվում: Մեզ մոտ հենց այդպիսի դեպք կա. Ֆունկցիան սպասում է օգտատիրոջը, ով իր խնդիրները լուծելու փոխարեն ստիպված կլինի ուղղել այն։

Ճիշտ:

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

Հատված N9. Զուր ցուցիչ

void SearchableTableEmitter::collectTableEntries(
    GenericTable &Table, const std::vector<Record *> &Items) {
  ....
  RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
  if (!Ty)                                                              // <=
    PrintFatalError(Twine("Field '") + Field.Name + "' of table '" +
                    Table.Name + "' has incompatible type: " +
                    Ty->getAsString() + " vs. " +                       // <=
                    TI->getType()->getAsString());
   ....
}

PVS-Studio-ի նախազգուշացում. V522 [CWE-476] «Ty» զրոյական ցուցիչի անջատում կարող է տեղի ունենալ: SearchableTableEmitter.cpp 614

Կարծում եմ՝ ամեն ինչ պարզ է և բացատրություն չի պահանջում։

Հատված N10. Տառասխալ

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 նախազգուշացում. V570 «Identifier->Type» փոփոխականը վերագրված է իրեն: FormatTokenLexer.cpp 249

Անիմաստ է իրեն փոփոխական վերագրելը: Ամենայն հավանականությամբ նրանք ցանկանում էին գրել.

Identifier->Type = Question->Type;

Հատված N11. Կասկածելի ընդմիջում

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 նախազգուշացում. V622 [CWE-478] Մտածեք ստուգելու «անջատիչ» հայտարարությունը: Հնարավոր է, որ առաջին «գործի» օպերատորը բացակայում է: SystemZAsmParser.cpp 652

Սկզբում շատ կասկածելի օպերատոր կա կոտրել. Մոռացե՞լ եք այստեղ ուրիշ բան գրել։

Հատված N12. ցուցիչի ստուգում հղումից հեռացնելուց հետո

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 նախազգուշացում. V595 [CWE-476] «Callee» ցուցիչը օգտագործվել է նախքան այն ստուգվելը nullptr-ի դեմ: Ստուգեք տողերը՝ 172, 174. AMDGPUInline.cpp 172

Ինդեքսը Կալլի սկզբում անջատված է ֆունկցիան կանչելու պահին ստանալTTI.

Եվ հետո պարզվում է, որ այս ցուցիչը պետք է ստուգվի հավասարության համար nullptr:

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

Բայց արդեն ուշ է…

Հատված N13 - N...՝ ցուցիչի ստուգում հղումից հեռացնելուց հետո

Նախորդ կոդի հատվածում քննարկված իրավիճակը եզակի չէ։ Այստեղ հայտնվում է.

static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B,
                               bool isBinary, bool isPrecise = false) {
  ....
  Function *CalleeFn = CI->getCalledFunction();
  StringRef CalleeNm = CalleeFn->getName();                 // <=
  AttributeList CalleeAt = CalleeFn->getAttributes();
  if (CalleeFn && !CalleeFn->isIntrinsic()) {               // <=
  ....
}

PVS-Studio նախազգուշացում. V595 [CWE-476] «CalleeFn» ցուցիչը օգտագործվել է նախքան այն ստուգվելը nullptr-ի հետ: Ստուգեք տողերը՝ 1079, 1081: SimplifyLibCalls.cpp 1079

Եվ ահա.

void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
                            const Decl *Tmpl, Decl *New,
                            LateInstantiatedAttrVec *LateAttrs,
                            LocalInstantiationScope *OuterMostScope) {
  ....
  NamedDecl *ND = dyn_cast<NamedDecl>(New);
  CXXRecordDecl *ThisContext =
    dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());         // <=
  CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
                             ND && ND->isCXXInstanceMember());     // <=
  ....
}

PVS-Studio նախազգուշացում. V595 [CWE-476] «ND» ցուցիչը օգտագործվել է նախքան այն ստուգվելը nullptr-ի հետ: Ստուգեք տողերը՝ 532, 534: SemaTemplateInstantiateDecl.cpp 532

Եվ ահա.

  • V595 [CWE-476] «U» ցուցիչը օգտագործվել է նախքան nullptr-ի հետ ստուգումը: Ստուգեք տողերը՝ 404, 407: DWARFormValue.cpp 404
  • V595 [CWE-476] «ND» ցուցիչը օգտագործվել է նախքան nullptr-ի հետ ստուգումը: Ստուգեք տողերը՝ 2149, 2151: SemaTemplateInstantiate.cpp 2149

Եվ հետո ես չհետաքրքրվեցի ուսումնասիրել V595 համարով նախազգուշացումները։ Այսպիսով, ես չգիտեմ, արդյոք կան ավելի շատ նմանատիպ սխալներ, բացի այստեղ թվարկվածներից: Ամենայն հավանականությամբ կա։

Հատված N17, N18՝ Կասկածելի տեղաշարժ

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

PVS-Studio նախազգուշացում. V629 [CWE-190] Մտածեք ստուգելու '~(Չափ - 1) << 1' արտահայտությունը: 32-բիթանոց արժեքի բիթային տեղաշարժ՝ 64-բիթանոց տիպի հետագա ընդլայնմամբ: AArch64AddressingModes.h 260

Հնարավոր է, որ դա վրիպակ չէ, և կոդը աշխատում է ճիշտ այնպես, ինչպես նախատեսված է: Բայց սա ակնհայտորեն շատ կասկածելի վայր է և պետք է ստուգվի:

Ասենք փոփոխականը չափ հավասար է 16-ի, այնուհետև կոդի հեղինակը նախատեսել է այն ստանալ փոփոխականում NImms իմաստը:

1111111111111111111111111111111111111111111111111111111111100000

Այնուամենայնիվ, իրականում արդյունքը կլինի.

0000000000000000000000000000000011111111111111111111111111100000

Փաստն այն է, որ բոլոր հաշվարկները կատարվում են 32-բիթանոց չստորագրված տիպի միջոցով: Եվ միայն այն ժամանակ, այս 32-բիթանոց անստորագիր տեսակը անուղղակիորեն կընդլայնվի uint64_t. Այս դեպքում ամենակարևոր բիթերը կլինեն զրո:

Դուք կարող եք շտկել իրավիճակը հետևյալ կերպ.

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

Նմանատիպ իրավիճակ. V629 [CWE-190] Մտածեք ստուգելու «Immr << 6» արտահայտությունը: 32-բիթանոց արժեքի բիթային տեղաշարժ՝ 64-բիթանոց տիպի հետագա ընդլայնմամբ: AArch64AddressingModes.h 269

Հատված N19. Բացակայում է հիմնաբառը ուրիշ?

void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
  ....
  if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
    // VOP2b (v_add_u32, v_sub_u32 ...) dpp use "vcc" token.
    // Skip it.
    continue;
  } if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {    // <=
    Op.addRegWithFPInputModsOperands(Inst, 2);
  } else if (Op.isDPPCtrl()) {
    Op.addImmOperands(Inst, 1);
  } else if (Op.isImm()) {
    // Handle optional arguments
    OptionalIdx[Op.getImmTy()] = I;
  } else {
    llvm_unreachable("Invalid operand type");
  }
  ....
}

PVS-Studio նախազգուշացում. V646 [CWE-670] Մտածեք ստուգել հավելվածի տրամաբանությունը: Հնարավոր է, որ «այլ» հիմնաբառը բացակայում է: AMDGPUAsmParser.cpp 5655

Այստեղ ոչ մի սխալ չկա։ Առաջինի այն ժամանակվա բլոկից ի վեր if ավարտվում է շարունակել, ուրեմն կապ չունի, բանալի բառ կա ուրիշ կամ ոչ. Ամեն դեպքում, կոդը կաշխատի նույն կերպ: Դեռ բաց թողնված ուրիշ ծածկագիրը դարձնում է ավելի անհասկանալի և վտանգավոր: Եթե ​​ապագայում շարունակել անհետանում է, կոդը կսկսի գործել բոլորովին այլ կերպ: Իմ կարծիքով ավելի լավ է ավելացնել ուրիշ.

Հատված N20. Նույն տիպի չորս տառասխալներ

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 նախազգուշացումներ.

  • V655 [CWE-480] Տողերը միացված էին, բայց չեն օգտագործվում: Մտածեք ստուգելու «Արդյունք + Name.str()» արտահայտությունը: Symbol.cpp 32
  • V655 [CWE-480] Տողերը միացված էին, բայց չեն օգտագործվում: Ստուգեք «Արդյունք + «(ObjC Class)» + Name.str()» արտահայտությունը: Symbol.cpp 35
  • V655 [CWE-480] Տողերը միացված էին, բայց չեն օգտագործվում: Ստուգեք «Արդյունք + «(ObjC Class EH)» + Name.str()» արտահայտությունը: Symbol.cpp 38
  • V655 [CWE-480] Տողերը միացված էին, բայց չեն օգտագործվում: Ստուգեք «Արդյունք + «(ObjC IVar)» + Name.str()» արտահայտությունը: Symbol.cpp 41

Պատահաբար += օպերատորի փոխարեն օգտագործվում է + օպերատորը: Արդյունքը ձևավորումներ են, որոնք զուրկ են իմաստից:

Հատված N21. Չսահմանված վարքագիծ

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

Փորձեք ինքներդ գտնել վտանգավոր ծածկագիրը: Եվ սա նկար է ուշադրությունը շեղելու համար, որպեսզի անմիջապես չնայես պատասխանին.

LLVM 8-ում սխալների հայտնաբերում PVS-Studio անալիզատորի միջոցով

PVS-Studio նախազգուշացում. V708 [CWE-758] Օգտագործվում է վտանգավոր կառուցվածք՝ «FeaturesMap[Op] = FeaturesMap.size()», որտեղ «FeaturesMap»-ը «քարտեզ» դասի է: Սա կարող է հանգեցնել անորոշ պահվածքի: RISCVCompressInstEmitter.cpp 490

Խնդրի գիծ.

FeaturesMap[Op] = FeaturesMap.size();

Եթե ​​տարր Op չի գտնվել, այնուհետև քարտեզում ստեղծվում է նոր տարր և այնտեղ գրվում է այս քարտեզի տարրերի քանակը: Պարզապես անհայտ է, թե արդյոք գործառույթը կկանչվի Չափս նոր տարր ավելացնելուց առաջ կամ հետո:

Հատված N22-N24. Կրկնվող առաջադրանքներ

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

PVS-Studio նախազգուշացում. V519 [CWE-563] «NType» փոփոխականին տրվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 1663, 1664: MachOObjectFile.cpp 1664

Չեմ կարծում, որ այստեղ իրական սխալ կա: Պարզապես անհարկի կրկնվող հանձնարարություն։ Բայց դեռ կոպիտ սխալ.

Նմանապես:

  • V519 [CWE-563] «B.NDesc» փոփոխականին տրվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Փոփոխականին տրվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 59, 61. coff2yaml.cpp 61

Հատված N25-N27. Ավելի շատ վերահանձնումներ

Հիմա եկեք տեսնենք վերահանձնման մի փոքր այլ տարբերակ:

bool Vectorizer::vectorizeLoadChain(
    ArrayRef<Instruction *> Chain,
    SmallPtrSet<Instruction *, 16> *InstructionsProcessed) {
  ....
  unsigned Alignment = getAlignment(L0);
  ....
  unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(),
                                                 StackAdjustedAlignment,
                                                 DL, L0, nullptr, &DT);
  if (NewAlign != 0)
    Alignment = NewAlign;
  Alignment = NewAlign;
  ....
}

PVS-Studio-ի նախազգուշացում՝ V519 [CWE-563] «Հավասարեցում» փոփոխականին նշանակվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 1158, 1160: LoadStoreVetorizer.cpp 1160

Սա շատ տարօրինակ կոդ է, որը, ըստ երևույթին, պարունակում է տրամաբանական սխալ: Սկզբում փոփոխական Հավասարեցում արժեք է նշանակվում՝ կախված պայմանից: Եվ հետո հանձնարարությունը կրկին տեղի է ունենում, բայց հիմա առանց ստուգման:

Նմանատիպ իրավիճակներ կարելի է տեսնել այստեղ.

  • V519 [CWE-563] «Էֆեկտներ» փոփոխականին տրվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] «ExpectNoDerefChunk» փոփոխականին տրվում են արժեքներ երկու անգամ հաջորդաբար: Միգուցե սա սխալմունք է։ Ստուգեք տողերը՝ 4970, 4973. SemaType.cpp 4973

Հատված N28. Միշտ ճիշտ վիճակ

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

PVS-Studio նախազգուշացում. V547 [CWE-571] «nextByte != 0x90» արտահայտությունը միշտ ճիշտ է: X86DisassemblerDecoder.cpp 379

Ստուգելն իմաստ չունի։ Փոփոխական հաջորդԲայթ միշտ չէ, որ հավասար է արժեքին 0x90, որը բխում է նախորդ ստուգումից։ Սա ինչ-որ տրամաբանական սխալ է:

Հատված N29 - N...՝ Միշտ ճիշտ/կեղծ պայմաններ

Անալիզատորը տալիս է բազմաթիվ նախազգուշացումներ, որ ամբողջ վիճակը (V547) կամ դրա մի մասը (V560) միշտ ճիշտ է կամ կեղծ: Հաճախ դրանք իրական սխալներ չեն, այլ պարզապես անփույթ կոդ, մակրո ընդլայնման արդյունք և այլն: Այնուամենայնիվ, իմաստ ունի նայել այս բոլոր նախազգուշացումներին, քանի որ ժամանակ առ ժամանակ տեղի են ունենում իրական տրամաբանական սխալներ: Օրինակ, կոդի այս բաժինը կասկածելի է.

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 նախազգուշացում. V560 [CWE-570] Պայմանական արտահայտության մի մասը միշտ կեղծ է՝ RegNo == 0xe: ARMdisassembler.cpp 939

0xE հաստատունը տասնորդական 14 արժեքն է: Փորձաքննություն RegNo == 0xe իմաստ չունի, քանի որ եթե RegNo > 13, ապա ֆունկցիան կավարտի իր կատարումը։

Կային բազմաթիվ այլ նախազգուշացումներ V547 և V560 ID-ներով, բայց ինչպես և V595, ես շահագրգռված չէի ուսումնասիրել այս զգուշացումները։ Արդեն պարզ էր, որ ես բավականաչափ նյութ ունեի հոդված գրելու համար :)։ Հետևաբար, անհայտ է, թե այս տեսակի քանի սխալներ կարելի է հայտնաբերել LLVM-ում՝ օգտագործելով PVS-Studio:

Ես ձեզ օրինակ կտամ, թե ինչու է այս հրահրող գործոնների ուսումնասիրությունը ձանձրալի: Անալիզատորը միանգամայն իրավացի է հետևյալ կոդի համար նախազգուշացում տալով. Բայց սա սխալ չէ։

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

PVS-Studio Զգուշացում. V547 [CWE-570] «!HasError» արտահայտությունը միշտ կեղծ է: UnwrappedLineParser.cpp 1635

Հատված N30. Կասկածելի վերադարձ

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 նախազգուշացում. V612 [CWE-670] Անվերապահ «վերադարձ» օղակի մեջ: R600OptimizeVectorRegisters.cpp 63

Սա կա՛մ սխալ է, կա՛մ հատուկ տեխնիկա, որը կոչված է ինչ-որ բան բացատրելու ծածկագիրը կարդացող ծրագրավորողներին: Այս դիզայնը ինձ ոչինչ չի բացատրում և շատ կասկածելի է թվում: Ավելի լավ է այդպես չգրես :)։

Հոգնե՞լ եք: Հետո թեյ կամ սուրճ պատրաստելու ժամանակն է։

LLVM 8-ում սխալների հայտնաբերում PVS-Studio անալիզատորի միջոցով

Նոր ախտորոշմամբ հայտնաբերված թերությունները

Հին դիագնոստիկայի 30 ակտիվացումը կարծում եմ բավական է։ Հիմա տեսնենք, թե ինչ հետաքրքիր բաներ կարելի է գտնել անալիզատորում հետո հայտնված նոր ախտորոշմամբ նախորդ ստուգումներ. Այս ընթացքում C++ անալիզատորին ավելացվել է ընդհանուր նշանակության 66 ախտորոշիչ:

Հատված N31. անհասանելի ծածկագիր

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

  CtorDtorsByPriority.clear();

  return Error::success();
}

PVS-Studio նախազգուշացում. V779 [CWE-561] Հայտնաբերվել է անհասանելի կոդ: Հնարավոր է, որ սխալ կա: ExecutionUtils.cpp 146

Ինչպես տեսնում եք, օպերատորի երկու մասնաճյուղերը if ավարտվում է օպերատորին զանգով վերադարձնել. Համապատասխանաբար, բեռնարկղը CtorDtorsByPriority երբեք չի մաքրվի:

Հատված N32. անհասանելի ծածկագիր

bool LLParser::ParseSummaryEntry() {
  ....
  switch (Lex.getKind()) {
  case lltok::kw_gv:
    return ParseGVEntry(SummaryID);
  case lltok::kw_module:
    return ParseModuleEntry(SummaryID);
  case lltok::kw_typeid:
    return ParseTypeIdEntry(SummaryID);                        // <=
    break;                                                     // <=
  default:
    return Error(Lex.getLoc(), "unexpected summary kind");
  }
  Lex.setIgnoreColonInIdentifiers(false);                      // <=
  return false;
}

PVS-Studio նախազգուշացում՝ V779 [CWE-561] Հայտնաբերվել է անհասանելի կոդ: Հնարավոր է, որ սխալ կա: LLParser.cpp 835

Հետաքրքիր իրավիճակ. Եկեք նախ նայենք այս վայրին.

return ParseTypeIdEntry(SummaryID);
break;

Առաջին հայացքից թվում է, թե այստեղ սխալ չկա։ Կարծես օպերատորին կոտրել այստեղ կա լրացուցիչ մեկը, և դուք կարող եք պարզապես ջնջել այն: Այնուամենայնիվ, ամեն ինչ այնքան էլ պարզ չէ:

Անալիզատորը նախազգուշացում է տալիս տողերի վրա.

Lex.setIgnoreColonInIdentifiers(false);
return false;

Եվ իսկապես, այս կոդը անհասանելի է: Բոլոր դեպքերը անցնել ավարտվում է օպերատորի զանգով վերադարձնել. Իսկ հիմա մենակ անիմաստ կոտրել այնքան էլ անվնաս չի թվում: Թերևս ճյուղերից մեկը պետք է ավարտվի դրանով կոտրելբայց ոչ վերադարձնել?

Հատված N33. Բարձր բիթերի պատահական վերակայում

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

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

PVS-Studio նախազգուշացում. V784 Բիթային դիմակի չափը փոքր է առաջին օպերանդի չափից: Սա կհանգեցնի ավելի բարձր բիթերի կորստի: RuntimeDyld.cpp 815

Խնդրում ենք նկատի ունենալ, որ գործառույթը getStubAlignment վերադարձնում է տեսակը չստորագրված. Եկեք հաշվարկենք արտահայտության արժեքը՝ ենթադրելով, որ ֆունկցիան վերադարձնում է 8 արժեքը.

~(getStubAlignment() - 1)

~ (8u-1)

0xFFFFFFFF8u

Այժմ ուշադրություն դարձրեք, որ փոփոխականը Տվյալների չափը ունի 64-բիթանոց անստորագիր տեսակ: Ստացվում է, որ DataSize & 0xFFFFFFF8u գործողությունը կատարելիս բոլոր երեսուներկու բարձր կարգի բիթերը կզրոյացվեն: Ամենայն հավանականությամբ, սա այն չէ, ինչ ցանկանում էր ծրագրավորողը։ Ես կասկածում եմ, որ նա ցանկացել է հաշվարկել՝ DataSize & 0xFFFFFFFFFFFFFFFFFF8u։

Սխալը շտկելու համար դուք պետք է գրեք սա.

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

Կամ այսպես.

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

Հատված N34. Չհաջողվեց բացահայտ տիպի քաղվածք

template <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
                      SmallVectorImpl<T> &ScaledMask) {
  assert(0 < Scale && "Unexpected scaling factor");
  int NumElts = Mask.size();
  ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
  ....
}

PVS-Studio նախազգուշացում. V1028 [CWE-190] Հնարավոր արտահոսք: Մտածեք «NumElts * Scale» օպերատորի ձուլման օպերանդները «size_t» տեսակի, այլ ոչ թե արդյունքի: X86ISelLowering.h 1577

Բացահայտ տիպի ձուլումը օգտագործվում է տիպի փոփոխականները բազմապատկելիս վարարումից խուսափելու համար int. Այնուամենայնիվ, բացահայտ տիպի ձուլումն այստեղ չի պաշտպանում արտահոսքից: Նախ, փոփոխականները կբազմապատկվեն, և միայն դրանից հետո բազմապատկման 32-բիթանոց արդյունքը կընդլայնվի մինչև տեսակի. չափը_թ.

Հատված N35. Չհաջողվեց 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;
  }
  ....
}

V778 [CWE-682] Գտնվել են երկու նմանատիպ ծածկագրի բեկորներ: Հնարավոր է, սա տառասխալ է, և «Op1» փոփոխականը պետք է օգտագործվի «Op0»-ի փոխարեն: InstCombineCompares.cpp 5507

Այս նոր հետաքրքիր ախտորոշումը բացահայտում է իրավիճակներ, երբ կոդի մի կտոր պատճենվել է, և դրա մեջ որոշ անուններ սկսել են փոխվել, բայց մի տեղ չեն ուղղել այն:

Խնդրում ենք նկատի ունենալ, որ երկրորդ բլոկում դրանք փոխվել են Op0 մասին Op1. Բայց մի տեղ չեն ուղղել. Ամենայն հավանականությամբ այն պետք է գրվեր այսպես.

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

Հատված N36. Փոփոխական շփոթություն

struct Status {
  unsigned Mask;
  unsigned Mode;

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

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

PVS-Studio նախազգուշացում. V1001 [CWE-563] «Mode» փոփոխականը նշանակված է, բայց չի օգտագործվում գործառույթի ավարտին: SIModeRegister.cpp 48

Շատ վտանգավոր է ֆունկցիայի արգումենտներին տալ դասի անդամների նույն անունները: Շատ հեշտ է շփոթվել։ Մեզ մոտ հենց այդպիսի դեպք կա. Այս արտահայտությունը իմաստ չունի.

Mode &= Mask;

Ֆունկցիայի փաստարկը փոխվում է: Այսքանը: Այս փաստարկն այլևս չի օգտագործվում: Ամենայն հավանականությամբ, դուք պետք է գրեիք այսպես.

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

Հատված N37. Փոփոխական շփոթություն

class SectionBase {
  ....
  uint64_t Size = 0;
  ....
};

class SymbolTableSection : public SectionBase {
  ....
};

void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type,
                                   SectionBase *DefinedIn, uint64_t Value,
                                   uint8_t Visibility, uint16_t Shndx,
                                   uint64_t Size) {
  ....
  Sym.Value = Value;
  Sym.Visibility = Visibility;
  Sym.Size = Size;
  Sym.Index = Symbols.size();
  Symbols.emplace_back(llvm::make_unique<Symbol>(Sym));
  Size += this->EntrySize;
}

Զգուշացում PVS-Studio՝ V1001 [CWE-563] «Չափ» փոփոխականը նշանակված է, բայց գործառույթի ավարտին չի օգտագործվում: Object.cpp 424

Իրավիճակը նման է նախորդին. Պետք է գրել.

this->Size += this->EntrySize;

Հատված N38-N47. Մոռացել են ստուգել ցուցանիշը

Նախկինում մենք դիտարկել ենք ախտորոշիչ հրահրման օրինակներ V595. Դրա էությունն այն է, որ սկզբում ցուցիչը հանվում է, և միայն դրանից հետո ստուգվում: Երիտասարդ ախտորոշում V1004 իմաստով հակառակն է, բայց նաև բացահայտում է բազմաթիվ սխալներ: Այն բացահայտում է իրավիճակներ, երբ ցուցիչը սկզբում ստուգվել է, իսկ հետո մոռացվել է դա անել: Դիտարկենք 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());  // <=
  ....
}

PVS-Studio-ի նախազգուշացում. V1004 [CWE-476] «Ptr» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 729, 738. TargetTransformInfoImpl.h 738

Փոփոխական Պտր կարող է հավասար լինել nullptr, ինչպես վկայում է ստուգումը.

if (Ptr != nullptr)

Այնուամենայնիվ, այս ցուցիչից ներքևում նշված է առանց նախնական ստուգման.

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

Դիտարկենք նմանատիպ մեկ այլ դեպք.

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

PVS-Studio-ի նախազգուշացում. V1004 [CWE-476] «FD» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 3228, 3231: CGDebugInfo.cpp 3231

Ուշադրություն դարձրեք նշանին FD. Համոզված եմ, որ խնդիրը հստակ տեսանելի է, և հատուկ բացատրություն չի պահանջվում։

Եվ հետագայում.

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

PVS-Studio-ի նախազգուշացում. V1004 [CWE-476] «PtrTy» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 960, 965. InterleavedLoadCombinePass.cpp 965

Ինչպե՞ս պաշտպանվել ձեզ նման սխալներից: Ավելի ուշադիր եղեք Code-Review-ում և օգտագործեք PVS-Studio ստատիկ անալիզատորը՝ կանոնավոր կերպով ստուգելու ձեր կոդը:

Այս տեսակի սխալներով այլ կոդերի հատվածներ մեջբերելու իմաստ չկա: Հոդվածում ես կթողնեմ միայն նախազգուշացումների ցանկը.

  • V1004 [CWE-476] «Expr» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 1049, 1078: DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] «PI» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 733, 753: LegacyPassManager.cpp 753
  • V1004 [CWE-476] «StatepointCall» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] «RV» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 2263, 2268։ TGParser.cpp 2268
  • V1004 [CWE-476] «CalleeFn» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 1081, 1096: SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] «TC» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 1819, 1824: Driver.cpp 1824

Հատված N48-N60. Ոչ թե կրիտիկական, այլ թերություն (հնարավոր հիշողության արտահոսք)

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

PVS-Studio նախազգուշացում. V1023 [CWE-460] «Ստրատեգիաներ» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-isel-fuzzer.cpp 58

Նման տարայի վերջում տարր ավելացնելու համար std::վեկտոր > չես կարող ուղղակի գրել xxx.push_back (նոր X), քանի որ անուղղակի փոխակերպում չկա X* в std::unique_ptr.

Ընդհանուր լուծում գրելն է xxx.emplace_back (նոր X)քանի որ այն կազմում է մեթոդ emplace_back կառուցում է տարր ուղղակիորեն իր փաստարկներից և, հետևաբար, կարող է օգտագործել բացահայտ կոնստրուկտորներ:

Դա անվտանգ չէ։ Եթե ​​վեկտորը լի է, ապա հիշողությունը վերաբաշխվում է: Հիշողության վերաբաշխման գործողությունը կարող է ձախողվել, ինչի արդյունքում բացառություն է բացվում std::bad_alloc. Այս դեպքում ցուցիչը կկորչի, և ստեղծված օբյեկտը երբեք չի ջնջվի:

Անվտանգ լուծում ստեղծելն է unique_ptrորը կունենա ցուցիչ, նախքան վեկտորը կփորձի վերաբաշխել հիշողությունը.

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

Քանի որ C++14, դուք կարող եք օգտագործել 'std::make_unique':

xxx.push_back(std::make_unique<X>())

Այս տեսակի թերությունը կարևոր չէ LLVM-ի համար: Եթե ​​հիշողությունը հնարավոր չէ հատկացնել, կոմպիլյատորը պարզապես կդադարի: Այնուամենայնիվ, երկարատև հավելվածների համար գործարկման ժամանակ, որը չի կարող պարզապես դադարեցվել, եթե հիշողության բաշխումը ձախողվի, սա կարող է իսկական տհաճ սխալ լինել:

Այսպիսով, չնայած այս կոդը գործնական վտանգ չի ներկայացնում LLVM-ի համար, ես օգտակար գտա խոսել այս սխալի օրինակի մասին, և որ PVS-Studio անալիզատորը սովորել է նույնականացնել այն:

Այս տեսակի այլ նախազգուշացումներ.

  • V1023 [CWE-460] «Անցումներ» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ PassManager.h 546
  • V1023 [CWE-460] «AAs» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ AliasAnalysis.h 324
  • V1023 [CWE-460] «Entries» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] «AllEdges» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ CFGMST.h 268
  • V1023 [CWE-460] «VMaps» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ SimpleLoopUnswitch.cpp 2012 թ
  • V1023 [CWE-460] «Records» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ FDRLogBuilder.h 30
  • V1023 [CWE-460] «PendingSubmodules» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ ModuleMap.cpp 810
  • V1023 [CWE-460] «Օբյեկտներ» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ DebugMap.cpp 88
  • V1023 [CWE-460] «Ստրատեգիաներ» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 685
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 686
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 688
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 689
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 690
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 691
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 692
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 693
  • V1023 [CWE-460] «Modifiers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ llvm-stress.cpp 694
  • V1023 [CWE-460] «Operands» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ GlobalISelEmitter.cpp 1911 թ
  • V1023 [CWE-460] «Stash» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] «Matchers» կոնտեյների մեջ առանց սեփականատիրոջ ցուցիչ է ավելացվել «emplace_back» մեթոդով: Բացառության դեպքում հիշողության արտահոսք տեղի կունենա։ GlobalISelEmitter.cpp 2702

Ամփոփում

Ընդհանուր առմամբ 60 նախազգուշացում եմ արել, հետո դադարեցրել: Կա՞ն այլ թերություններ, որոնք PVS-Studio անալիզատորը հայտնաբերում է LLVM-ում: Այո, ունեմ. Այնուամենայնիվ, երբ ես հոդվածի համար ծածկագրեր էի գրում, ուշ երեկո էր, ավելի ճիշտ՝ նույնիսկ գիշեր, և ես որոշեցի, որ ժամանակն է այն անվանել օր։

Հուսով եմ, որ ձեզ հետաքրքիր է թվում և կցանկանաք փորձել PVS-Studio անալիզատորը:

Դուք կարող եք ներբեռնել անալիզատորը և ստանալ ականակիր ստեղնը այստեղից այս էջը.

Ամենակարևորը, պարբերաբար օգտագործեք ստատիկ վերլուծություն: Միանգամյա ստուգումներ, մեր կողմից իրականացված ստատիկ վերլուծության մեթոդաբանությունը հանրահռչակելու նպատակով և PVS-Studio-ն նորմալ սցենար չեն։

Հաջողություն ձեր կոդի որակի և հուսալիության բարելավման գործում:

LLVM 8-ում սխալների հայտնաբերում PVS-Studio անալիզատորի միջոցով

Եթե ​​ցանկանում եք կիսվել այս հոդվածով անգլիախոս լսարանի հետ, խնդրում ենք օգտագործել թարգմանության հղումը՝ Անդրեյ Կարպով: Փնտրեք սխալներ LLVM 8-ում PVS-Studio-ով.

Source: www.habr.com

Добавить комментарий