پیدا کردن اشکالات در 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: کپی-پیست

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

Fragment 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» حذف شود. Miscompilation.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) {

به راحتی می توان دید که حلقه شروع نمی شود. من فکر می کنم این نیز یک اشکال است و کد باید متفاوت نوشته شود.

گویا با آن برابری معروف خطاها مواجه شده ایم! یک اشتباه دیگری را می پوشاند :).

Fragment 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] ممکن است ارجاع مجدد نشانگر تهی «تست» انجام شود. Miscompilation.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] ممکن است عدم ارجاع اشاره گر تهی '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] ممکن است عدم ارجاع اشاره گر تهی «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] بازرسی عبارت 'switch' را در نظر بگیرید. این امکان وجود دارد که اولین اپراتور «مورد» گم شده باشد. 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

اشاره گر کالی در ابتدا در زمان فراخوانی تابع، ارجاع داده نمی شود getTTI.

و سپس معلوم می شود که این اشاره گر باید برای برابری بررسی شود nullptr:

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

ولی الان خیلی دیر است…

Fragment 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] عبارت '~(Size - 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 به پایان می رسد با ادامه دادن، پس مهم نیست، یک کلمه کلیدی وجود دارد دیگر یا نه. در هر صورت کد یکسان کار خواهد کرد. هنوز از دست رفته دیگر کد را نامشخص و خطرناک تر می کند. اگر در آینده ادامه دادن ناپدید می شود، کد کاملاً متفاوت شروع به کار می کند. به نظر من بهتر است اضافه شود دیگر.

Fragment 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] رشته ها به هم پیوسته بودند اما استفاده نمی شوند. عبارت "Result + Name.str()" را بررسی کنید. Symbol.cpp 32
  • V655 [CWE-480] رشته ها به هم پیوسته بودند اما استفاده نمی شوند. عبارت "Result + "(ObjC Class)" + Name.str()" را در نظر بگیرید. Symbol.cpp 35
  • V655 [CWE-480] رشته ها به هم پیوسته بودند اما استفاده نمی شوند. عبارت "Result + "(ObjC Class EH) " + Name.str()" را بررسی کنید. Symbol.cpp 38
  • V655 [CWE-480] رشته ها به هم پیوسته بودند اما استفاده نمی شوند. عبارت "Result + "(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' از کلاس 'map' است. این ممکن است منجر به رفتار نامشخص شود. 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] به متغیر 'Alignment' مقادیری دو بار متوالی اختصاص داده می شود. شاید این یک اشتباه باشد. بررسی خطوط: 1158، 1160. LoadStoreVetorizer.cpp 1160

این کد بسیار عجیب است که ظاهراً حاوی یک خطای منطقی است. در ابتدا متغیر هم ترازی یک مقدار بسته به شرایط اختصاص داده می شود. و سپس تخصیص دوباره اتفاق می افتد، اما اکنون بدون هیچ گونه بررسی.

موقعیت های مشابه را می توان در اینجا مشاهده کرد:

  • V519 [CWE-563] به متغیر "Effects" مقادیری دو بار متوالی اختصاص داده می شود. شاید این یک اشتباه باشد. بررسی خطوط: 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

چک کردن معنی نداره متغیر nextByte همیشه با ارزش برابر نیست 0x90، که از بررسی قبلی بر می آید. این نوعی خطای منطقی است.

Fragment 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 وجود داشت، اما مانند این 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 فعال سازی دیاگ های قدیمی کافی است. بیایید اکنون ببینیم چه چیزهای جالبی را می توان با تشخیص های جدیدی که پس از آن در آنالایزر ظاهر شد پیدا کرد قبلی چک ها در طول این مدت، در مجموع 66 تشخیص همه منظوره به آنالایزر C++ اضافه شد.

قطعه 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: کپی-پیست ناموفق

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] متغیر "Size" اختصاص داده شده است اما تا پایان تابع استفاده نمی شود. 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» به ظرف «Strategies» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-isel-fuzzer.cpp 58

برای اضافه کردن یک عنصر به انتهای یک ظرف مانند std:: vector > شما نمی توانید فقط بنویسید 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] یک اشاره گر بدون مالک به کانتینر «Passes» با روش «emplace_back» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. PassManager.h 546
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «AAs» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. AliasAnalysis.h 324
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Entries» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «AllEdges» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. CFGMST.h 268
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف 'VMaps' اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Records» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. FDRLogBuilder.h 30
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «PendingSubmodules» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. ModuleMap.cpp 810
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «اشیاء» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. DebugMap.cpp 88
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Strategies» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 685
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 686
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 688
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 689
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 690
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 691
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 692
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 693
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Modifiers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. llvm-stress.cpp 694
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به کانتینر «Operands» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] یک نشانگر بدون مالک با روش «emplace_back» به ظرف «Stash» اضافه می‌شود. در صورت استثنا، نشت حافظه رخ خواهد داد. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] یک اشاره گر بدون مالک با روش «emplace_back» به ظرف «Matchers» اضافه می شود. در صورت استثنا، نشت حافظه رخ خواهد داد. GlobalISelEmitter.cpp 2702

نتیجه

در کل 60 اخطار دادم و بعد متوقف شدم. آیا عیوب دیگری وجود دارد که آنالایزر PVS-Studio در LLVM تشخیص می دهد؟ بله دارم. با این حال، وقتی داشتم قطعات کد مقاله را می نوشتم، اواخر عصر یا بهتر است بگوییم حتی شب بود، و تصمیم گرفتم که وقت آن است که آن را یک روز بنامم.

امیدوارم برای شما جالب بوده باشد و بخواهید تحلیلگر PVS-Studio را امتحان کنید.

می توانید آنالایزر را دانلود کنید و کلید مین یاب را در اینجا دریافت کنید این صفحه.

مهمتر از همه، به طور منظم از تجزیه و تحلیل استاتیک استفاده کنید. چک های یکباره، انجام شده توسط ما به منظور رایج کردن روش تجزیه و تحلیل استاتیک و PVS-Studio یک سناریوی عادی نیست.

در بهبود کیفیت و قابلیت اطمینان کد خود موفق باشید!

پیدا کردن اشکالات در LLVM 8 با استفاده از تحلیلگر PVS-Studio

اگر می خواهید این مقاله را با مخاطبان انگلیسی زبان به اشتراک بگذارید، لطفاً از پیوند ترجمه استفاده کنید: Andrey Karpov. یافتن اشکالات در LLVM 8 با PVS-Studio.

منبع: www.habr.com

اضافه کردن نظر