Знаходзім багі ў LLVM 8 з дапамогай аналізатара PVS-Studio

Знаходзім багі ў LLVM 8 з дапамогай аналізатара PVS-Studio
Прайшло больш за два гады з моманту апошняй праверкі кода праекта LLVM з дапамогай нашага аналізатара PVS-Studio. Давайце пераканаемся, што аналізатар PVS-Studio па-ранейшаму з'яўляецца якая лідыруе прыладай па выяўленні памылак і патэнцыйных уразлівасцяў. Для гэтага праверым і знойдзем новыя памылкі ў рэлізе LLVM 8.0.0.

Артыкул, які павінен быць напісаны

Калі шчыра, мне не хацелася пісаць гэты артыкул. Нецікава пісаць пра праект, які мы ўжо неаднаразова правяралі (1, 2, 3). Лепш напісаць пра нешта новае, але я не маю выбару.

Кожны раз, калі выходзіць новая версія LLVM ці абнаўляецца Clang Static Analyzer, у нас у пошце з'яўляюцца пытанні наступнага тыпу:

Глядзіце, новая версія Clang Static Analyzer навучылася знаходзіць новыя памылкі! Мне здаецца, актуальнасць выкарыстоўваць PVS-Studio памяншаецца. Clang знаходзіць больш памылак, чым раней і даганяе па магчымасцях PVS-Studio. Што вы пра гэта думаеце?

На гэта мне заўсёды жадаецца адказаць нешта ў духу:

Мы таксама не сядзім без справы! Мы істотна палепшылі магчымасці аналізатара PVS-Studio. Так што не хвалюйцеся, мы працягваем лідзіраваць, як і раней.

Нажаль, гэта дрэнны адказ. У ім няма proof-аў. І менавіта таму зараз я пішу гэты артыкул. Такім чынам, праект 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] Ёсць падобныя sub-expressions 'Name.startswith(«avx512.mask.permvar.»)' у лівую і правы '||' operator. 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 Гэта падобныя sub-expressions 'CXNameRange_WantQualifier' у правым і правым '|' operator. 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] Вырашыце, што '?:' абаронца працуе ў розных выпадках, калі ён быў выяўлены. '?:' абаронца мае вялікае priority, чым '==' абаронца. 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] Вызваленне null-патэрнаў 'LHS' мае такую ​​колькасць месцаў. TGParser.cpp 2152

Калі паказальнік LHS апынецца нулявым, тое павінна быць выдадзена папярэджанне. Аднак замест гэтага адбудзецца разнайменне гэтага самага нулявога паказальніка: LHS->getAsString().

Гэта вельмі тыповая сітуацыя, калі памылка хаваецца ў апрацоўшчыку памылак, бо іх ніхто не тэстуе. Статычныя аналізатары правяраюць увесь дасягальны код, незалежна ад таго, як часта ён выкарыстоўваецца. Гэта вельмі добры прыклад, як статычны аналіз дапаўняе іншыя методыкі тэсціравання і абароны ад памылак.

Аналагічная памылка апрацоўкі паказальніка RHS дапушчаная ў кодзе крыху ніжэй: V522 [CWE-476] Пабочныя эфекты null pointer 'RHS' might take place. 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] Зніжэнне null pointer 'ProgClone' мае такую ​​месца. Miscompilation.cpp 601

У пачатку разумны паказальнік ProgClone перастае валодаць аб'ектам:

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

Фактычна, зараз ProgClone - Гэта нулявы паказальнік. Таму крыху ніжэй павінна адбыцца разнайменаванне нулявога паказальніка:

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

Але, насамрэч, гэтага не адбудзецца! Звярніце ўвагу, што цыкл насамрэч не выконваецца.

У пачатку кантэйнер MiscompiledFunctions чысціцца:

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] Папярэджанне null pointer 'Test' might take place. Miscompilation.cpp 709

Ізноў тая ж самая сітуацыя. Спачатку змесціва аб'екта перамяшчаецца, а затым ён выкарыстоўваецца як ні ў чым не хаджала. Я ўсё гушчару сустракаю гэтую сітуацыю ў кодзе праграм, пасля таго, як у З++ з'явілася семантыка перасоўвання. За гэта я і кахаю мову 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] Знішчэнне нульовага інтэр'еру 'Type' цягам такую ​​месца. 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] Папярэджанне null pointer 'Ty' might take place. 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'' variable is assigned to itself. FormatTokenLexer.cpp 249

Няма сэнсу прысвойваць зменную саму сабе. Хутчэй за ўсё хацелі напісаць:

Identifier->Type = Question->Type;

Фрагмент N11: Падазроны break

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

Папярэджанне PVS-Studio: V622 [CWE-478] Consider inspecting the 'switch' statement. Гэта магчыма, што першы 'case' абаронца з'яўляецца запуск. 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] The 'Callee' pointer быў выкарыстаны да таго, што быў зняволены да nullptr. Check lines: 172, 174. AMDGPUInline.cpp 172

паказальнік Callee у пачатку разназываецца ў момант выкліку функцыі getTTI.

А затым аказваецца, што гэты паказальнік трэба правяраць на роўнасць. 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] The 'CalleeFn' pointer быў выкарыстаны да таго, што быў зняволены да nullptr. Check lines: 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] The 'ND' pointer быў выкарыстаны да таго, што быў зняволены да nullptr. Check lines: 532, 534. SemaTemplateInstantiateDecl.cpp 532

І тут:

  • V595 [CWE-476] The 'U' pointer быў выкарыстаны да яго быў зняволены да nullptr. Check lines: 404, 407. DWARFFormValue.cpp 404
  • V595 [CWE-476] The 'ND' pointer быў выкарыстаны да яго быў зняволены да nullptr. Check lines: 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] Consider inspecting the '~ (Size - 1) << 1' expression. Заціхшы shifting of the 32-bit valor with subsequent expansion to the 64-bit typ. AArch64AddressingModes.h 260

Магчыма, гэта не памылка, і код працуе менавіта так, як і задумана. Але гэта відавочна вельмі падазронае месца, і яго трэба праверыць.

Дапушчальны, зменная памер роўная 16, і тады аўтар кода планаваў атрымаць у зменнай NImms значэнне:

1111111111111111111111111111111111111111111111111111111111100000

Аднак, насамрэч, атрымаецца значэнне:

0000000000000000000000000000000011111111111111111111111111100000

Справа ў тым, што ўсе вылічэнні адбываюцца з выкарыстаннем 32-бітнага тыпу unsigned. І толькі затым, гэты 32-бітны беззнакавы тып будзе няяўна пашыраны да uint64_t. Пры гэтым старэйшыя біты апынецца нулявымі.

Выправіць сітуацыю можна так:

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

Аналагічная сітуацыя: V629 [CWE-190] Consider inspecting the 'Immr << 6' expression. Заціхшы shifting of the 32-bit valor with subsequent expansion to the 64-bit typ. 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] Прадстаўнік крытыкі прыкладанняў лагічнай. Гэта магчыма, што 'else' keyword is missing. AMDGPUAsmParser.cpp 5655

Памылкі тут няма. Так як then-блок першага 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] strings былі concatenated, але не выкарыстоўваюцца. Consider inspecting the 'Result + Name.str()' expression. Symbol.cpp 32
  • V655 [CWE-480] strings былі concatenated, але не выкарыстоўваюцца. Consider inspecting the 'Result + "(ObjC Class)" + Name.str()' expression. Symbol.cpp 35
  • V655 [CWE-480] strings былі concatenated, але не выкарыстоўваюцца. Consider inspecting the 'Result + "(ObjC Class EH)" + Name.str()' expression. Symbol.cpp 38
  • V655 [CWE-480] strings былі concatenated, але не выкарыстоўваюцца. Consider inspecting the 'Result + "(ObjC IVar)" + Name.str()' expression. 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' з 'map' class. Гэта можа быць умацаваны behavior. 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] The 'NType' variable з'яўляецца памерам twice successively. Perhaps this is a mistake. Check lines: 1663, 1664. MachOObjectFile.cpp 1664

Думаю, сапраўднай памылкі тут няма. Проста лішняе паўтаральнае прысвойванне. Але ўсё роўна ляп.

Аналагічна:

  • V519 [CWE-563] Вікіпедыя 'B.NDesc' з'яўляецца памерам двух success. Perhaps this is a mistake. Check lines: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Вартасць лічыцца памерам два разы паспяхова. Perhaps this is a mistake. Check lines: 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] З''наладжванне' variable з'яўляецца памер twice successively. Perhaps this is a mistake. Check lines: 1158, 1160. LoadStoreVectorizer.cpp 1160

Гэта вельмі дзіўны код, які па ўсёй бачнасці змяшчае лагічную памылку. У пачатку, зменнай Выраўноўванне прысвойваецца значэнне ў залежнасці ад умовы. А затым ізноў адбываецца прысвойванне, але зараз ужо без усялякай праверкі.

Аналагічныя сітуацыі можна ўбачыць тут:

  • V519 [CWE-563] 'эфэкты' variable з'яўляюцца памерамі twice successively. Perhaps this is a mistake. Check lines: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] Вядомы пункт 'ExpectNoDerefChunk' з'яўляецца памерам двух цвёрдых. Perhaps this is a mistake. Check lines: 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] Expression 'nextByte! = 0x90' is always true. X86DisassemblerDecoder.cpp 379

Праверка не мае сэнсу. Пераменная nextByte заўсёды не роўна значэнню 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] Apart of conditional expression is always false: RegNo == 0xe. ARMDisassembler.cpp 939

Канстанта 0xE гэта значэнне 14 у дзесятковай сістэме. Праверка RegNo == 0xe не мае сэнсу, бо калі RegNo > 13, то функцыя завершыць сваё выкананне.

Было мноства іншых папярэджанняў з ідэнтыфікатарам V547 і V560, але, як і ў выпадку з 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] Expression '! HasError' is always false. 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] An unconditional 'return' within a loop. 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] Unreachable code пазначаны. Гэта магчыма, што памылка з'яўляецца. 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] Unreachable code detected. Гэта магчыма, што памылка з'яўляецца. 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 Частка бітавай маскі з'яўляецца меншай, чым памер першай operand. Гэтая прычына знішчэння вялікіх бітаў. RuntimeDyld.cpp 815

Звярніце ўвагу, што функцыя getStubAlignment вяртае тып без знака. Вылічым значэнне выразу, калі выказаць здагадку, што функцыя верне значэнне 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFF8u

Цяпер звярніце ўвагу, што зменная DataSize мае 64-бітны беззнакавы тып. Атрымліваецца, што пры выкананні аперацыі DataSize & 0xFFFFFFF8u усе трыццаць два старэйшых біта будуць абнулены. Хутчэй за ўсё гэта не тое, што хацеў праграміст. Падазраю, што ён жадаў вылічыць: DataSize & 0xFFFFFFFFFFFFFFFFF8u.

Каб выправіць памылку, трэба напісаць так:

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] Possible overflow. Прадстаўнік каштуючы абаронцаў '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] Two similar code fragments were found. Змяняецца, гэта тыпа і 'Op1' variable можа быць выкарыстана ўстаноўленай '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' variable з'яўляецца, але не задзейнічаны ў канцы функцыі. 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] Вярнуцца 'Size' variable з'яўляецца, але не залежыць ад канца функцыі. 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] The 'Ptr' pointer быў выкарыстаны павольна пасля таго, як ён быў зняволены да nullptr. Check lines: 729, 738. TargetTransformInfoImpl.h 738

пераменная Ptr можа быць роўна 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] The 'FD' pointer быў выкарыстаны павольна пасля таго, як ён быў зняволены да nullptr. Check lines: 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] The 'PtrTy' pointer быў выкарыстаны павольна пасля таго, як ён быў зняволены да nullptr. Check lines: 960, 965. InterleavedLoadCombinePass.cpp 965

Як абараніцца ад такіх памылак? Будзьце больш уважлівымі на Code-Review і выкарыстоўвайце для рэгулярнай праверкі кода статычны аналізатар PVS-Studio.

Прыводзіць іншыя фрагменты кода з памылкамі дадзенага выгляду сэнсу няма. Пакіну ў артыкуле толькі спіс папярэджанняў:

  • V1004 [CWE-476] The 'Expr' pointer быў выкарыстаны павольна пасля таго, як быў зняволены да nullptr. Check lines: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] П'Інтэрнэт пісьменнік быў выкарыстаны бездапаможна пасля таго, як быў зняволены да nullptr. Check lines: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] The 'StatepointCall' pointer быў ужыты па-заўсёднаму, калі ён быў зняволены да nullptr. Check lines: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] The 'RV' pointer быў выкарыстаны павольна пасля таго, як быў зняволены да nullptr. Check lines: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] The 'CalleeFn' pointer быў выкарыстаны павольна пасля таго, як быў зняволены да nullptr. Check lines: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] The 'TC' pointer быў ужыты недастаткова пасля таго, як быў зняволены да nullptr. Check lines: 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] Pointer bez vlastníka je doplněn o 'Strategies' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-isel-fuzzer.cpp 58

Для дадання элемента ў канец кантэйнера тыпу std::vector > нельга проста напісаць xxx.push_back(new X), так як няма няяўнага пераўтварэнні з X* в std::unique_ptr.

Распаўсюджаным рашэннем з'яўляецца напісанне xxx.emplace_back(new 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] Пытанне без ўладальніка з'яўляецца 'Passes' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. PassManager.h 546
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'AAs' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. AliasAnalysis.h 324
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Entries' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'AllEdges' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. CFGMST.h 268
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'VMaps' кантэйнер з 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Records' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. FDRLogBuilder.h 30
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'PendingSubmodules' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. ModuleMap.cpp 810
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Objects' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. DebugMap.cpp 88
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Strategies' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 685
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 686
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 688
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 689
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 690
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 691
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 692
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 693
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'modifiers' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. llvm-stress.cpp 694
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Operands' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Stash' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] Pointer bez vlastníka je doplněn o 'Machinery' container by 'emplace_back' метад. У памяшканні леак будзе здавацца ў выпадку з exception. GlobalISelEmitter.cpp 2702

Заключэнне

Агулам я выпісаў 60 папярэджанняў, пасля чаго спыніўся. Ці ёсць іншыя дэфекты, якія выяўляе ў LLVM аналізатар PVS-Studio? Да ёсць. Аднак, калі я выпісваў фрагменты кода для артыкула, надышоў позні вечар, дакладней, нават ноч, і я вырашыў, што час закругляцца.

Спадзяюся, вам было цікава, і вы захочаце паспрабаваць аналізатар PVS-Studio.

Вы можаце спампаваць аналізатар і атрымаць тральны ключ на гэтай старонцы.

Самае галоўнае, выкарыстоўвайце статычны аналіз рэгулярна. Разавыя праверкі, якія выконваюцца намі з мэтай папулярызацыі метадалогіі статычнага аналізу і PVS-Studio не з'яўляюцца нармальным сцэнарам.

Удачы ў паляпшэнні якасці і надзейнасці кода!

Знаходзім багі ў LLVM 8 з дапамогай аналізатара PVS-Studio

Калі хочаце падзяліцца гэтым артыкулам з англамоўнай аўдыторыяй, то прашу выкарыстаць спасылку на пераклад: Andrey Karpov. Finding Bugs in LLVM 8 with PVS-Studio.

Крыніца: habr.com

Дадаць каментар