العثور على الأخطاء في 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 لم تكن موجودة منذ عامين. قررت تسليط الضوء عليها في قسم منفصل لإظهار تطور PVS-Studio بوضوح.

العيوب التي تم تحديدها عن طريق التشخيص كانت موجودة منذ عامين

الجزء رقم 1: نسخ ولصق

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.")' على يسار ويمين '||' المشغل أو العامل. الترقية التلقائية.cpp 73

يتم التحقق مرتين من أن الاسم يبدأ بالسلسلة الفرعية "avx512.mask.permvar." في الشيك الثاني، من الواضح أنهم أرادوا كتابة شيء آخر، لكنهم نسوا تصحيح النص المنسوخ.

الجزء رقم 2: خطأ مطبعي

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' على يسار ويمين '|' المشغل أو العامل. سينديكس.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

في البداية مؤشر ذكي com.ProgClone يتوقف عن امتلاك الكائن:

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

في الواقع، الآن com.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

أعتقد أن كل شيء واضح ولا يحتاج إلى شرح.

الجزء رقم 10: خطأ مطبعي

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 يتم تعيين المتغير "المعرف->النوع" لنفسه. فورماتتوكينليكسير.cpp 249

ليس هناك فائدة من تخصيص متغير لنفسه. على الأرجح أنهم أرادوا أن يكتبوا:

Identifier->Type = Question->Type;

القطعة رقم 11: استراحة مشبوهة

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

الفهرس كالي في البداية يتم إلغاء الإشارة إليها في وقت استدعاء الوظيفة 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] تم استخدام مؤشر "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، ثم خطط مؤلف الكود للحصول عليه في متغير نيمز معنى:

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] تم ربط السلاسل ولكن لم يتم استخدامها. فكر في فحص التعبير "Result + Name.str()". الرمز.cpp 32
  • V655 [CWE-480] تم ربط السلاسل ولكن لم يتم استخدامها. فكر في فحص التعبير "Result + "(ObjC Class)" + Name.str()'. الرمز.cpp 35
  • V655 [CWE-480] تم ربط السلاسل ولكن لم يتم استخدامها. فكر في فحص التعبير "Result + "(ObjC Class EH) " + Name.str()". الرمز.cpp 38
  • V655 [CWE-480] تم ربط السلاسل ولكن لم يتم استخدامها. ضع في اعتبارك فحص التعبير 'Result + "(ObjC IVar)" + Name.str()'. الرمز.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. LoadStoreVectorizer.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

التحقق لا معنى له. عامل 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] جزء من التعبير الشرطي يكون دائمًا خطأ: RegNo == 0xe. أرمديساسمبلير.cpp 939

الثابت 0xE هو القيمة 14 بالنظام العشري. فحص RegNo == 0xe لا معنى له لأنه إذا رقم التسجيل> 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

القطعة رقم 30: عودة مشبوهة

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] تم اكتشاف رمز لا يمكن الوصول إليه. من الممكن أن يكون هناك خطأ. التنفيذUtils.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 حجم قناع البت أقل من حجم المعامل الأول. سيؤدي هذا إلى فقدان البتات الأعلى. وقت التشغيلDyld.cpp 815

يرجى ملاحظة أن الوظيفة getStubAlignment نوع الإرجاع غير موقعة. دعونا نحسب قيمة التعبير، على افتراض أن الدالة ترجع القيمة 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFFF8u

الآن لاحظ أن المتغير حجم البيانات يحتوي على نوع غير موقع 64 بت. اتضح أنه عند إجراء عملية DataSize & 0xFFFFFF8u، سيتم إعادة تعيين جميع البتات ذات الترتيب العالي البالغ عددها 0 بت إلى الصفر. على الأرجح، ليس هذا ما أراده المبرمج. أظن أنه يريد حساب: DataSize & 8xFFFFFFFFFFFFFFXNUMXu.

لإصلاح الخطأ عليك كتابة هذا:

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

يتم استخدام صب النوع الصريح لتجنب التجاوز عند ضرب متغيرات النوع مادبا. ومع ذلك، فإن صب النوع الصريح هنا لا يحمي من الفائض. أولاً، سيتم ضرب المتغيرات، وعندها فقط سيتم توسيع نتيجة الضرب 32 بت إلى النوع size_t.

الجزء 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] تم تعيين متغير "الوضع" ولكن لا يتم استخدامه بنهاية الوظيفة. 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] تم تعيين متغير "الحجم" ولكن لا يتم استخدامه بنهاية الوظيفة. الكائن.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

كيف تحمي نفسك من مثل هذه الأخطاء؟ كن أكثر اهتمامًا بمراجعة التعليمات البرمجية واستخدم محلل 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

لإضافة عنصر إلى نهاية الحاوية مثل الأمراض المنقولة جنسيا::ناقل > لا يمكنك الكتابة فقط xxx.push_back(X جديد)، لأنه لا يوجد تحويل ضمني من X* в الأمراض المنقولة جنسيا::unique_ptr.

الحل المشترك هو الكتابة xxx.emplace_back(X جديد)لأنه يجمع: الطريقة emplace_back ينشئ عنصرًا مباشرةً من وسيطاته، وبالتالي يمكنه استخدام مُنشئات صريحة.

انه غير امن. إذا كان المتجه ممتلئًا، فسيتم إعادة تخصيص الذاكرة. قد تفشل عملية إعادة تخصيص الذاكرة، مما يؤدي إلى طرح استثناء الأمراض المنقولة جنسيا::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". سيحدث تسرب للذاكرة في حالة وجود استثناء. مدير المرور.h 546
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "AAs" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. تحليل الاسم المستعار.ح 324
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "الإدخالات" بواسطة طريقة "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] تتم إضافة مؤشر بدون مالك إلى حاوية "السجلات" بواسطة طريقة "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] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 685
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 686
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 688
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 689
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 690
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 691
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 692
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 693
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعدلات" بواسطة طريقة "emplace_back". سيحدث تسرب للذاكرة في حالة وجود استثناء. llvm-stress.cpp 694
  • V1023 [CWE-460] تتم إضافة مؤشر بدون مالك إلى حاوية "المعاملات" بواسطة طريقة "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.

المصدر: www.habr.com

إضافة تعليق