لقد مر عامان منذ آخر مرة قمنا فيها بالتحقق من رمز مشروع 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 بشكل كبير ، فلا تقلق - فنحن لا نزال الأفضل.
ولكن هذا إجابة سيئة ، أخشى. لا تقدم أي أدلة ، وهذا هو السبب في أنني أكتب هذه المقالة. لذلك ، لقد راجعت LLVM مرة أخرى ووجدت الكثير من الأخطاء من جميع الأنواع. تلك التي أعجبتني أكثر ستناقش أكثر. يتعذر على Clang Static Analyzer اكتشاف هذه الأخطاء (أو يجعل العملية مزعجة للغاية) - ويمكننا ذلك. وبالمناسبة ، استغرق الأمر مني ليلة واحدة فقط لأكتب كل تلك الأخطاء.
المقالة ، رغم ذلك ، استغرقت عدة أسابيع حتى أكمل. لم أستطع إحضار نفسي لوضع المواد المجمعة في نص :).
بالمناسبة ، إذا كنت تتساءل عن التقنيات التي يستخدمها PVS-Studio للكشف عن الأخطاء ونقاط الضعف ، ألق نظرة على هذا
المنشور .
التشخيصات الجديدة والقائمة
كما قلت سابقًا ، تم إجراء آخر فحصات عديدة لـ LLVM قبل عامين ، وتم إصلاح الأخطاء التي تم العثور عليها بعد ذلك بواسطة المؤلفين. هذه المادة سوف تظهر جزء جديد من الأخطاء. كيف تأتي هناك أخطاء جديدة على الإطلاق؟ هناك ثلاثة أسباب:
- مشروع LLVM يتطور ؛ المؤلفون تعديل التعليمات البرمجية الموجودة وإضافة رمز جديد. كل من الأجزاء المعدلة والجديدة تحتوي بشكل طبيعي على أخطاء جديدة. هذه الحقيقة هي حجة قوية لتشغيل التحليل الثابت بانتظام بدلاً من حين لآخر. يعد تنسيق مقالاتنا مثاليًا لعرض إمكانيات PVS-Studio ، ولكن لا علاقة له بتحسين جودة الكود أو جعل إصلاح الأخطاء أقل تكلفة. هل تستخدم التحليل الثابت بانتظام!
- نقوم بتعديل التشخيصات الموجودة وتحسينها ، مما يتيح للمحلل اكتشاف الأخطاء التي لم يتمكن من اكتشافها من قبل.
- تم تعزيز PVS-Studio بالتشخيصات الجديدة ، التي لم تكن موجودة قبل عامين. قمت بتجميع هذه التحذيرات في قسم منفصل بحيث يتم رؤية تقدم PVS-Studio بشكل أكثر وضوحًا.
العيوب الموجودة بواسطة التشخيصات الموجودة
مقتطف لا. 1: نسخ ولصقstatic bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) { if (Name == "addcarryx.u32" ||
رسالة تشخيص PVS-Studio:
V501 [CWE-570] يوجد Name.startswith تعبيرات فرعية متطابقة ("avx512.mask.permvar.") "إلى اليسار وإلى يمين" || " المشغل. AutoUpgrade.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' إلى اليسار وإلى يمين "|" المشغل. CIndex.cpp 7245
يتم استخدام ثابت المسمى
CXNameRange_WantQualifier مرتين بسبب خطأ مطبعي.
مقتطف لا. 3: الارتباك على أسبقية المشغل 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())
من الواضح أن هذا خطأ. يجب أن يكون المتغير
Index الذي أراد المبرمج التحقق من 0/1. لإصلاح الكود ، يجب إحاطة المشغل الثلاثي بأقواس:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
المشغل الثلاثي هو في الواقع صعب للغاية وقد يؤدي إلى أخطاء منطقية. استخدمه بعناية ولا تتردد في وضع أقواس إضافية حوله. تمت مناقشة هذا الموضوع بمزيد من التفصيل
هنا ، في قسم "احذر من المشغل؟ وأرفقه بين قوسين".
قصاصات لا. 4 ، 5: مؤشر لاغية 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 خاليًا ، فمن المتوقع أن ينشئ البرنامج تحذيرًا. بدلاً من ذلك ، سيتم dereference هذا المؤشر فارغة جداً:
LHS-> getAsString () .
من المعتاد جدًا احتواء معالجات الأخطاء على الأخطاء لأن المطورين لا يختبرونها بشكل صحيح. يقوم المحللون الثابتون بفحص جميع التعليمات البرمجية القابلة للوصول بغض النظر عن عدد مرات تنفيذها بالفعل. هذا مثال جيد على كيفية استكمال التحليل الثابت لوسائل الاختبار والحماية البرمجية الأخرى.
تم العثور على معالج خاطئ مشابه لمؤشر
RHS أكثر قليلاً: V522 [CWE-476] قد يتم إلغاء تحديد مؤشر الخالي من المؤشر "RHS". TGParser.cpp 2186
مقتطف لا. 6: استخدام المؤشر بعد التحرك static Expected<bool> ExtractBlocks(....) { .... std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap); .... BD.setNewProgram(std::move(ProgClone));
رسالة تشخيص PVS-Studio: V522 [CWE-476] قد تتم عملية إلغاء تأشير المؤشر الخالي "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) {
من الواضح أن الحلقة لن تبدأ. أعتقد أنه خطأ ، وكان المقصود من الكود أن يبدو بطريقة مختلفة.
أعتقد أن ما نراه هنا هو تماثل الخطأ السيئ السمعة ، حيث يعمل أحد الأخطاء كقناع لآخر :).
مقتطف لا. 7: استخدام المؤشر بعد التحرك 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));
رسالة تشخيص PVS-Studio: V522 [CWE-476] قد تتم عملية إلغاء تأشير المؤشر الخالي "اختبار". Miscompilation.cpp 709
هذا واحد يشبه الحالة السابقة. يتم نقل محتويات الكائن أولاً ثم استخدامها كما لو لم يحدث شيء. تزايد هذا الخطأ شيوعًا بعد إضافة دلالات النقل إلى C ++. هذا ما يعجبني في هذه اللغة! يتم منحك طرقًا جديدة لإطلاق النار على قدمك ، مما يعني أن PVS-Studio سيكون لديك دائمًا عمل للقيام به :).
مقتطف لا. 8: مؤشر لاغية 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>";
مقتطف لا. 9: مؤشر لاغية void SearchableTableEmitter::collectTableEntries( GenericTable &Table, const std::vector<Record *> &Items) { .... RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); if (!Ty)
رسالة تشخيص 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;
رسالة تشخيص PVS-Studio:
V570 يتم تعيين متغير "المعرف -> النوع" لنفسه. FormatTokenLexer.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
هناك بيان
استراحة مشبوه للغاية في البداية. لا ينبغي أن يكون هناك شيء آخر هنا؟
مقتطف لا. 12: التحقق من المؤشر بعد إلغاء التسجيل 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
يتم أولاً
إلغاء تحديد مؤشر
Callee عند
استدعاء دالة
getTTI .
ثم اتضح أنه يجب التحقق من المؤشر
بحثًا عن
nullptr :
if (!Callee || Callee->isDeclaration())
بعد فوات الأوان ...
قصاصات لا. 13 - لا ....: التحقق من المؤشر بعد إلغاء التسجيلالمثال السابق ليس فريدًا. تم العثور على نفس المشكلة في هذا المقتطف:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B, bool isBinary, bool isPrecise = false) { .... Function *CalleeFn = CI->getCalledFunction(); StringRef CalleeNm = CalleeFn->getName();
رسالة تشخيص 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());
رسالة تشخيص PVS-Studio: V595 [CWE-476] تم استخدام مؤشر "ND" قبل أن يتم التحقق منه ضد nullptr. خطوط التحقق: 532 ، 534. SemaTemplateInstantiateDecl.cpp 532
و هنا:
- V595 [CWE-476] تم استخدام مؤشر "U" قبل أن يتم التحقق منه مقابل nullptr. خطوط التحقق: 404 ، 407. DWARFFormValue.cpp 404
- V595 [CWE-476] تم استخدام مؤشر "ND" قبل أن يتم التحقق منه مقابل nullptr. خطوط التحقق: 2149 ، 2151. SemaTemplateInstantiate.cpp 2149
ثم فقدت الاهتمام بتتبع تحذيرات V595 ، لذا لا يمكنني أن أخبرك ما إذا كانت هناك أخطاء أخرى من هذا النوع إلى جانب تلك الموضحة أعلاه. أراهن هناك.
قصاصات لا. 17 ، 18: تحول مشبوه static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize, uint64_t &Encoding) { .... unsigned Size = RegSize; .... uint64_t NImms = ~(Size-1) << 1; .... }
رسالة تشخيص PVS-Studio:
V629 [CWE-190] فكر في فحص تعبير '~ (الحجم - 1) << 1'. تحويل البت لقيمة 32 بت مع توسع لاحق إلى نوع 64 بت. AArch64AddressingModes.h 260
قد يكون هذا الرمز صحيحًا بالفعل ، لكنه يبدو غريبًا ويحتاج إلى فحص.
افترض أن متغير
الحجم له قيمة 16 ؛ ثم من المتوقع أن يحصل متغير
NImms على القيمة التالية:
1111111111111111111111111111111111111111111111111111111111100000
ولكن في الواقع سوف تحصل على القيمة:
0000000000000000000000000000000011111111111111111111111111100000
يحدث هذا لأن جميع العمليات الحسابية تتم على نوع 32 بت غير موقَّع ، وعندها فقط يتم الترويج لها ضمنيًا إلى
uint64_t ، مع استبعاد البتات الأكثر أهمية.
يمكن إصلاح المشكلة كما يلي:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
خطأ آخر من هذا النوع: V629 [CWE-190] فكر في فحص تعبير "Immr << 6". تحويل البت لقيمة 32 بت مع توسع لاحق إلى نوع 64 بت. AArch64AddressingModes.h 269
مقتطف لا. 19: هل فقدت كلمة مفتاحية أخرى ؟ void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) { .... if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
رسالة تشخيص PVS-Studio:
V646 [CWE-670] خذ بعين الاعتبار فحص منطق التطبيق. من المحتمل أن الكلمة الرئيسية "أخرى" مفقودة. AMDGPUAsmParser.cpp 5655
هذا واحد ليس خطأ. منذ ذلك
الحين ،
تستمر المجموعة الأولى
إذا انتهت العبارة ، فلا يهم إذا كانت تحتوي على كلمة رئيسية أخرى أم لا. السلوك سيكون هو نفسه في أي حال. ومع ذلك ، فإن
الشيء المفقود يجعل الشفرة أقل قابلية للقراءة ، وبالتالي ، يحتمل أن تكون خطرة. إذا اختفت المتابعة في يوم من الأيام ، فسيتغير السلوك بشكل كبير. أوصي بشدة بإضافة
آخر .
مقتطف لا. 20: أربعة أخطاء مطبعية 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();
رسائل تشخيص PVS-Studio:
- V655 [CWE-480] كانت السلاسل متسلسلة ولكن غير مستخدمة. النظر في فحص التعبير 'النتيجة + Name.str ()'. Symbol.cpp 32
- V655 [CWE-480] كانت السلاسل متسلسلة ولكن غير مستخدمة. النظر في فحص التعبير "النتيجة +" (فئة ObjC) "+ Name.str ()" التعبير. Symbol.cpp 35
- V655 [CWE-480] كانت السلاسل متسلسلة ولكن غير مستخدمة. النظر في فحص التعبير "النتيجة +" (ObjC Class EH) "+ Name.str ()" التعبير. Symbol.cpp 38
- V655 [CWE-480] كانت السلاسل متسلسلة ولكن غير مستخدمة. النظر في فحص التعبير "النتيجة +" (ObjC IVar) "+ Name.str ()" التعبير. Symbol.cpp 41
استخدم المبرمج بطريق الخطأ عامل التشغيل + بدلاً من + = وانتهى الأمر بأربعة بنى لا معنى لها.
مقتطف لا. 21: سلوك غير محدد 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(); } } }
حاول اكتشاف الأخطاء بنفسك أولاً. لقد أضفت الصورة حتى لا تطلع على الإجابة فورًا:
رسالة تشخيص PVS-Studio:
V708 [CWE-758] يتم استخدام البناء الخطير: "FeaturesMap [Op] = FeaturesMap.size ()" ، حيث تكون "FeaturesMap" من فئة "map". هذا قد يؤدي إلى سلوك غير محدد. RISCVCompressInstEmitter.cpp 490
الخط الخاطئ هو هذا واحد:
FeaturesMap[Op] = FeaturesMap.size();
إذا لم يتم العثور على عنصر
Op ، يقوم البرنامج بإنشاء عنصر جديد في الخريطة ويعين له إجمالي عدد العناصر في هذه الخريطة. أنت لا تعرف ما إذا كان سيتم استدعاء وظيفة
الحجم قبل أو بعد إضافة العنصر الجديد.
قصاصات لا. 22 - لا 24: تكرار المهام Error MachOObjectFile::checkSymbolTable() const { .... } else { MachO::nlist STE = getSymbolTableEntry(SymDRI); NType = STE.n_type;
رسالة تشخيص 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
قصاصات لا. 25 - رقم 27: المزيد من المهام المكررةتتعامل هذه الإصدارات مع إصدارات مختلفة قليلاً من المهام المكررة.
bool Vectorizer::vectorizeLoadChain( ArrayRef<Instruction *> Chain, SmallPtrSet<Instruction *, 16> *InstructionsProcessed) { .... unsigned Alignment = getAlignment(L0); .... unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(), StackAdjustedAlignment, DL, L0, nullptr, &DT); if (NewAlign != 0) Alignment = NewAlign; Alignment = NewAlign; .... }
رسالة تشخيص PVS-Studio: V519 [CWE-563] تم تعيين متغير "المحاذاة" للقيم مرتين متتاليتين. ربما هذا خطأ. خطوط التحقق: 1158 ، 1160. LoadStoreVectorizer.cpp 1160
هذا مقتطف غريب جدًا ، وربما يحتوي على خطأ منطقي. يتم تعيين قيمة "
المحاذاة" أولاً على أساس الشرط ، ثم يتم تعيين القيمة مرة أخرى ، ولكن دون أي فحص مسبق.
عيوب مماثلة:
- V519 [CWE-563] تم تعيين متغير "Effects" لقيم مرتين متتاليتين. ربما هذا خطأ. خطوط التحقق: 152 ، 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] تم تعيين متغير "ExpectNoDerefChunk" لقيم مرتين متتاليتين. ربما هذا خطأ. خطوط التحقق: 4970 ، 4973. SemaType.cpp 4973
مقتطف لا. 28: دائما الحالة الحقيقية 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)
رسالة تشخيص PVS-Studio:
V547 [CWE-571] التعبير 'nextByte! = 0x90' صحيح دائمًا. X86DisassemblerDecoder.cpp 379
الشيك لا معنى له. لا
يكون متغير
nextByte مساوياً لـ
0x90 : إنه يتبع منطقياً فقط من الفحص السابق. هذا يجب أن يكون بعض خطأ المنطق.
قصاصات لا. 29 - لا ....: دائمًا صحيح / خطأهناك العديد من التحذيرات حول شرط كامل (
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.
إليك مثال لتوضيح السبب في أن التحقق من هذه التحذيرات ممل. المحلل هو الصحيح تماما عند إصدار تحذير على التعليمات البرمجية التالية. لكنه ما زال ليس خطأ.
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
إنها إما خطأ أو تقنية ترميز محددة تهدف إلى توصيل فكرة ما إلى زملائها في المبرمجين. بالنسبة لي ، لا يخبرني أي شيء إلا أنه رمز مشبوه للغاية. من فضلك لا تكتب رمز مثل هذا :).
الشعور بالتعب؟ حسنا ، لقد حان الوقت لصنع بعض الشاي أو القهوة.
العيوب التي عثر عليها التشخيص الجديد
أعتقد أن 30 مثالًا كافيًا للتشخيصات الحالية. الآن ، لنرى ما إذا كان بإمكاننا العثور على أي شيء مثير للاهتمام من خلال التشخيصات الجديدة ، التي تمت إضافتها بعد الفحص
السابق . على مدى العامين الماضيين ، تم تمديد وحدة محلل C ++ مع 66 تشخيص جديد.
مقتطف لا. 31: رمز غير قابل للوصول 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] تم اكتشاف رمز
يتعذر الوصول إليه. من الممكن وجود خطأ. ExecuteUtils.cpp 146
كما ترى ، ينتهي كلا فرعي العبارة
if ببيان
إرجاع ، مما يعني
أنه لن يتم مسح حاوية
CtorDtorsByPriority أبدًا.
مقتطف لا. 32: رمز غير قابل للوصول 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);
رسالة تشخيص PVS-Studio: V779 [CWE-561] تم اكتشاف رمز يتعذر الوصول إليه. من الممكن وجود خطأ. LLParser.cpp 835
هذا واحد مثير للاهتمام. ألقِ نظرة على هذا الجزء أولاً:
return ParseTypeIdEntry(SummaryID); break;
يبدو أنه لا يوجد شيء غريب حول هذا الرمز ؛ بيان
الاستراحة غير ضروري ويمكن إزالته بأمان. لكنها ليست بهذه البساطة.
يتم تشغيل التحذير بواسطة الأسطر التالية:
Lex.setIgnoreColonInIdentifiers(false); return false;
في الواقع ، هذا الرمز غير قابل للوصول. جميع تسميات العلبة الخاصة ببيان
التبديل تنتهي
بعودة ،
والكسر الوحيد الذي لا معنى له لا يبدو ضارًا بعد الآن! ماذا لو كان من المفترض أن ينتهي أحد الفروع
باستراحة بدلاً من
العودة ؟
مقتطف لا. 33: تطهير عرضي من البتات الأكثر أهمية 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)
0xFFFFFFF8u
لاحظ الآن أن نوع متغير
DataSize غير 64 بت غير موقعة. لذلك اتضح أن تنفيذ العملية DataSize & 0xFFFFFFF8 سيؤدي إلى مسح جميع البتات الـ 32 الأكثر أهمية في القيمة. لا أعتقد أن المبرمج أراد ذلك. ربما قصدوها أن تكون DataSize & 0xFFFFFFFFFFFFFFFFFF8u.
لإصلاح الخطأ ، يجب إعادة كتابة الكود كما يلي:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
أو مثل هذا:
DataSize &= ~(getStubAlignment() - 1ULL);
مقتطف لا. 34: سيئة تحويل نوع صريح 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
يتم استخدام تحويل النوع الصريح لتجنب حدوث تجاوز سعة عند ضرب متغيرات type
int . في هذه الحالة ، مع ذلك ، لا يعمل لأن الضرب سيحدث أولاً وعندها فقط سيتم ترقية النتيجة 32 بت لكتابة
size_t .
مقتطف لا. 35: نسخة سيئة لصق 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()));
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; }
مقتطف لا. 36: متغيرات مختلطة 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; };
مقتطف لا. 37: متغيرات مختلطة class SectionBase { .... uint64_t Size = 0; .... }; class SymbolTableSection : public SectionBase { .... }; void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type, SectionBase *DefinedIn, uint64_t Value, uint8_t Visibility, uint16_t Shndx, uint64_t Size) { .... Sym.Value = Value; Sym.Visibility = Visibility; Sym.Size = Size; Sym.Index = Symbols.size(); Symbols.emplace_back(llvm::make_unique<Symbol>(Sym)); Size += this->EntrySize; }
رسالة تشخيص PVS-Studio: V1001 [CWE-563] تم تعيين متغير "الحجم" ولكن لا يتم استخدامه بحلول نهاية الوظيفة. Object.cpp 424
هذا واحد يشبه المثال السابق. الإصدار الصحيح:
this->Size += this->EntrySize;
قصاصات لا. 38 - رقم 47: مفقود مؤشر الاختيارنظرنا إلى بعض الأمثلة على تحذير
V595 سابقًا. ما يكتشفه هو موقف عندما يتم إلغاء تأشير المؤشر أولاً ثم يتم فحصه فقط.
V1004 التشخيصي الجديد هو عكس ذلك ، ويكتشف الكثير من الأخطاء أيضًا. إنه يبحث عن مؤشرات تم اختبارها بالفعل ولا يتم اختبارها مرة أخرى عند الضرورة. فيما يلي بعض الأخطاء من هذا النوع الموجودة في كود LLVM.
int getGEPCost(Type *PointeeType, const Value *Ptr, ArrayRef<const Value *> Operands) { .... if (Ptr != nullptr) {
رسالة تشخيص PVS-Studio: V1004 [CWE-476] تم استخدام مؤشر "Ptr" بطريقة غير آمنة بعد أن تم التحقق منه ضد nullptr. خطوط التحقق: 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)
رسالة تشخيص 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) {
رسالة تشخيص 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
قصاصات لا. 48 - رقم 60: ليست حرجة ولكن لا يزال هناك عيب (تسرب ذاكرة محتمل) 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) لإلحاق عنصر بحاوية من النوع
std :: vector <std :: unique_ptr <X>> لأنه لا يوجد
ممثل ضمني من
X * إلى
std :: unique_ptr < X> .
يتمثل الحل الشائع في كتابة
xxx.emplace_back (X جديدة) نظرًا لأنه قابل
للترجمة : تقوم الطريقة
emplace_back بإنشاء العنصر مباشرةً من الوسيطات ، وبالتالي ، يمكنك استخدام
مُنشئين صريحين.
لكن هذا الحل ليس آمنا. إذا كان المتجه ممتلئًا ، سيتم إعادة تخصيص الذاكرة. قد تفشل هذه العملية وينتهي الأمر برفع استثناء
std :: bad_alloc . في هذه الحالة ، سيتم فقد المؤشر ولن يتمكن البرنامج من حذف الكائن الذي تم إنشاؤه.
الحل الأكثر أمانًا هو إنشاء
unique_ptr ، والذي سيحتفظ
بالمؤشر حتى يحاول المتجه إعادة تخصيص الذاكرة:
xxx.push_back(std::unique_ptr<X>(new X))
يسمح لك معيار C ++ 14 باستخدام 'std :: make_unique':
xxx.push_back(std::make_unique<X>())
هذا النوع من العيوب ليس له أي تأثير في LLVM. سيتم ببساطة إنهاء التجميع إذا فشل تخصيص الذاكرة. ومع ذلك ، قد يكون الأمر بالغ الأهمية في التطبيقات ذات فترة
التشغيل الطويلة ، والتي لا يمكن ببساطة إنهاؤها عند حدوث فشل في تخصيص الذاكرة.
لذا ، على الرغم من أن هذا الرمز ليس خطيرًا على LLVM ، فقد اعتقدت أنه لا يزال يتعين علي إخبارك بنمط الأخطاء هذا وحقيقة أن PVS-Studio يمكنه الآن اكتشافه.
حالات مماثلة أخرى:
- V1023 [CWE-460] يضاف مؤشر بدون مالك إلى حاوية "التمريرات" بواسطة طريقة "emplace_back". سوف يحدث تسرب للذاكرة في حالة حدوث استثناء. PassManager.h 546
- V1023 [CWE-460] A pointer without owner is added to the 'AAs' container by the 'emplace_back' method. A memory leak will occur in case of an exception. AliasAnalysis.h 324
- V1023 [CWE-460] A pointer without owner is added to the 'Entries' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] A pointer without owner is added to the 'AllEdges' container by the 'emplace_back' method. A memory leak will occur in case of an exception. CFGMST.h 268
- V1023 [CWE-460] A pointer without owner is added to the 'VMaps' container by the 'emplace_back' method. A memory leak will occur in case of an exception. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] A pointer without owner is added to the 'Records' container by the 'emplace_back' method. A memory leak will occur in case of an exception. FDRLogBuilder.h 30
- V1023 [CWE-460] A pointer without owner is added to the 'PendingSubmodules' container by the 'emplace_back' method. A memory leak will occur in case of an exception. ModuleMap.cpp 810
- V1023 [CWE-460] A pointer without owner is added to the 'Objects' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DebugMap.cpp 88
- V1023 [CWE-460] A pointer without owner is added to the 'Strategies' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 685
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 686
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 688
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 689
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 690
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 691
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 692
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 693
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 694
- V1023 [CWE-460] A pointer without owner is added to the 'Operands' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] A pointer without owner is added to the 'Stash' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] A pointer without owner is added to the 'Matchers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2702
استنتاج
كتبت 60 تحذيراً وتوقفت عند ذلك. هل عثر PVS-Studio على أي أخطاء أخرى في LLVM؟ نعم فعلت. لكن بينما كنت أكتب الأمثلة ، سقط الليل ، لذا قررت أن أتوقف.أتمنى أن تستمتع بقراءة هذا المقال وشجعك على تجربة محلل PVS-Studio بنفسك.تفضل بزيارة هذه الصفحة لتنزيل المحلل والحصول على مفتاح تجريبي.الأهم من ذلك ، استخدم التحليل الثابت بانتظام. الشيكات لمرة واحدة ، مثل تلك التي نقوم بها لتشجيع التحليل الثابت والترويج لـ PVS-Studio ، ليست هي السيناريو العادي.نتمنى لك التوفيق في تحسين جودة الشفرة وموثوقيتها!