بیش از دو سال از آخرین بررسی کد پروژه LLVM با استفاده از تحلیلگر PVS-Studio می گذرد. بیایید مطمئن شویم که تحلیلگر PVS-Studio هنوز یک ابزار پیشرو برای شناسایی خطاها و آسیب پذیری های احتمالی است. برای انجام این کار، خطاهای جدیدی را در نسخه LLVM 8.0.0 بررسی و پیدا می کنیم.
مقاله نوشته شود
راستش من نمی خواستم این مقاله را بنویسم. نوشتن در مورد پروژه ای که قبلا چندین بار بررسی کرده ایم جالب نیست (
هر بار که نسخه جدیدی از LLVM منتشر یا به روز می شود
ببینید، نسخه جدید Clang Static Analyzer یاد گرفته است که خطاهای جدید را پیدا کند! به نظر من ارتباط استفاده از PVS-Studio در حال کاهش است. Clang خطاهای بیشتری نسبت به قبل پیدا می کند و به قابلیت های PVS-Studio می رسد. چه فکری در این باره دارید؟
به این من همیشه می خواهم به چیزی مانند این پاسخ بدهم:
ما هم بیکار نمی نشینیم! ما قابلیت های آنالایزر PVS-Studio را به طور قابل توجهی بهبود بخشیده ایم. پس نگران نباشید، ما همچنان مانند گذشته پیشتاز هستیم.
متأسفانه این پاسخ بدی است. هیچ مدرکی در آن نیست. و به همین دلیل است که اکنون این مقاله را می نویسم. بنابراین پروژه LLVM یک بار دیگر بررسی شده و انواع خطاها در آن یافت شده است. اکنون مواردی را که برایم جالب به نظر می رسید را نشان می دهم. Clang Static Analyzer نمی تواند این خطاها را پیدا کند (یا انجام این کار با کمک آن بسیار ناخوشایند است). اما ما می توانیم. علاوه بر این، من تمام این خطاها را در یک شب پیدا کردم و یادداشت کردم.
اما نوشتن مقاله چندین هفته طول کشید. من فقط نمی توانستم خودم را مجبور کنم همه اینها را در متن قرار دهم :).
به هر حال، اگر علاقه مند هستید که در تحلیلگر PVS-Studio از چه فناوری هایی برای شناسایی خطاها و آسیب پذیری های احتمالی استفاده می شود، پیشنهاد می کنم با این موضوع آشنا شوید.
تشخیص جدید و قدیمی
همانطور که قبلا ذکر شد، حدود دو سال پیش پروژه LLVM یک بار دیگر بررسی شد و خطاهای یافت شده اصلاح شدند. اکنون این مقاله دسته جدیدی از خطاها را ارائه می دهد. چرا باگ های جدید پیدا شد؟ 3 دلیل برای این وجود دارد:
- پروژه LLVM در حال تکامل است، کدهای قدیمی را تغییر داده و کدهای جدید اضافه می کند. طبیعتاً خطاهای جدیدی در کدهای اصلاح شده و نوشته شده وجود دارد. این به وضوح نشان می دهد که تجزیه و تحلیل استاتیک باید به طور منظم و نه گهگاهی استفاده شود. مقالات ما به خوبی قابلیت های آنالایزر PVS-Studio را نشان می دهد، اما این ربطی به بهبود کیفیت کد و کاهش هزینه رفع خطاها ندارد. به طور مرتب از یک تحلیلگر کد استاتیک استفاده کنید!
- ما در حال نهایی کردن و بهبود تشخیص های موجود هستیم. بنابراین، آنالایزر می تواند خطاهایی را که در اسکن های قبلی متوجه آنها نشده است را شناسایی کند.
- تشخیص های جدیدی در 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:
دوبار بررسی می شود که نام با زیر رشته "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:
به نظر من این یک اشتباه بسیار زیباست. بله، می دانم که ایده های عجیبی در مورد زیبایی دارم :).
در حال حاضر، با توجه به
(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:
اگر اشاره گر 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:
هیچ فایده ای ندارد که یک متغیر را به خود اختصاص دهیم. به احتمال زیاد می خواستند بنویسند:
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:
در ابتدا یک اپراتور بسیار مشکوک وجود دارد شکستن. یادت رفت اینجا چیز دیگری بنویسی؟
قطعه 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:
اشاره گر کالی در ابتدا در زمان فراخوانی تابع، ارجاع داده نمی شود 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:
ممکن است یک اشکال نباشد و کد دقیقاً همانطور که در نظر گرفته شده است کار می کند. اما واضح است که این مکان بسیار مشکوک است و باید بررسی شود.
متغیر را فرض کنید اندازه برابر با 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:
در اینجا هیچ اشتباهی وجود ندارد. از آن زمان بلوک اول 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();
}
}
}
سعی کنید کد خطرناک را خودتان پیدا کنید. و این یک عکس برای منحرف کردن توجه است تا بلافاصله به پاسخ نگاه نکنید:
هشدار PVS-Studio:
خط مشکل:
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] به متغیر '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:
چک کردن معنی نداره متغیر nextByte همیشه با ارزش برابر نیست 0x90، که از بررسی قبلی بر می آید. این نوعی خطای منطقی است.
Fragment N29 - N...: شرایط همیشه درست/نادرست
آنالایزر هشدارهای زیادی صادر می کند که کل وضعیت (
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:
ثابت 0xE مقدار 14 در اعشار است. معاینه RegNo == 0xe منطقی نیست زیرا اگر RegNo > 13، سپس تابع اجرای خود را کامل می کند.
بسیاری از هشدارهای دیگر با شناسههای V547 و V560 وجود داشت، اما مانند این
من به شما مثالی می زنم که چرا مطالعه این محرک ها خسته کننده است. آنالیزور در صدور اخطار برای کد زیر کاملاً درست است. اما این یک اشتباه نیست.
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:
این یک خطا یا یک تکنیک خاص است که قصد دارد چیزی را برای برنامه نویسانی که کد را می خوانند توضیح دهد. این طرح چیزی را برای من توضیح نمی دهد و بسیار مشکوک به نظر می رسد. اینجوری ننویسی بهتره :).
خسته؟ سپس نوبت به تهیه چای یا قهوه می رسد.
عیوب شناسایی شده با تشخیص جدید
فکر می کنم 30 فعال سازی دیاگ های قدیمی کافی است. بیایید اکنون ببینیم چه چیزهای جالبی را می توان با تشخیص های جدیدی که پس از آن در آنالایزر ظاهر شد پیدا کرد
قطعه 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:
همانطور که می بینید هر دو شاخه اپراتور 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:
لطفا توجه داشته باشید که تابع 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:
ریخته گری نوع صریح برای جلوگیری از سرریز در هنگام ضرب متغیرهای نوع استفاده می شود 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;
}
....
}
این تشخیص جالب جدید موقعیتهایی را شناسایی میکند که در آن یک قطعه کد کپی شده و برخی از نامهای موجود در آن شروع به تغییر کردهاند، اما در یک مکان آن را اصلاح نکردهاند.
لطفا توجه داشته باشید که در بلوک دوم آنها تغییر کردند 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:
بسیار خطرناک است که آرگومان های تابع را با نام های مشابه اعضای کلاس قرار دهیم. گیج شدن بسیار آسان است. ما فقط یک چنین پرونده ای در پیش داریم. این عبارت معنی ندارد:
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: فراموش کردند شاخص را بررسی کنند
قبلاً نمونههایی از تحریک تشخیصی را بررسی کردیم
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:
برای اضافه کردن یک عنصر به انتهای یک ظرف مانند 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 یک سناریوی عادی نیست.
در بهبود کیفیت و قابلیت اطمینان کد خود موفق باشید!
اگر می خواهید این مقاله را با مخاطبان انگلیسی زبان به اشتراک بگذارید، لطفاً از پیوند ترجمه استفاده کنید: Andrey Karpov.
منبع: www.habr.com