PVS-Studio analizatorumuzdan istifadə edərək LLVM layihəsinin son kod yoxlamasından iki ildən çox vaxt keçdi. Gəlin əmin olaq ki, PVS-Studio analizatoru hələ də səhvləri və potensial zəiflikləri müəyyən etmək üçün aparıcı vasitədir. Bunun üçün biz LLVM 8.0.0 buraxılışında yeni xətaları yoxlayıb tapacağıq.
Yazılacaq məqalə
Düzünü desəm, bu yazını yazmaq istəməzdim. Artıq bir neçə dəfə yoxladığımız layihə haqqında yazmaq maraqlı deyil (
Hər dəfə LLVM-in yeni versiyası buraxılanda və ya yenilənir
Baxın, Clang Static Analyzer-in yeni versiyası yeni səhvləri tapmağı öyrəndi! Mənə elə gəlir ki, PVS-Studio-dan istifadənin aktuallığı getdikcə azalır. Clang əvvəlkindən daha çox səhv tapır və PVS-Studio-nun imkanlarına çatır. Bu barədə nə düşünürsünüz?
Buna həmişə belə bir cavab vermək istəyirəm:
Biz də boş oturmuruq! Biz PVS-Studio analizatorunun imkanlarını əhəmiyyətli dərəcədə təkmilləşdirmişik. Ona görə də narahat olmayın, biz əvvəlki kimi liderliyimizi davam etdiririk.
Təəssüf ki, bu pis cavabdır. Bunda heç bir dəlil yoxdur. Və buna görə də indi bu yazını yazıram. Belə ki, LLVM layihəsi bir daha yoxlanılıb və orada müxtəlif xətalar aşkar edilib. İndi mənə maraqlı görünənləri nümayiş etdirəcəyəm. Clang Static Analyzer bu səhvləri tapa bilmir (və ya onun köməyi ilə bunu etmək olduqca əlverişsizdir). Amma bacarırıq. Üstəlik bütün bu səhvləri bir axşam tapıb yazdım.
Ancaq məqalənin yazılması bir neçə həftə çəkdi. Bütün bunları mətnə yerləşdirə bilmədim :).
Yeri gəlmişkən, səhvləri və potensial zəiflikləri müəyyən etmək üçün PVS-Studio analizatorunda hansı texnologiyalardan istifadə olunduğu ilə maraqlanırsınızsa, mən bununla tanış olmağı təklif edirəm.
Yeni və köhnə diaqnostika
Artıq qeyd edildiyi kimi, təxminən iki il əvvəl LLVM layihəsi bir daha yoxlanılmış və aşkar edilmiş səhvlər düzəldilmişdir. İndi bu məqalə yeni səhvlər toplusunu təqdim edəcək. Niyə yeni səhvlər tapıldı? Bunun 3 səbəbi var:
- LLVM layihəsi inkişaf edir, köhnə kodu dəyişdirir və yeni kod əlavə edir. Təbii ki, dəyişdirilmiş və yazılmış kodda yeni səhvlər var. Bu, açıq şəkildə göstərir ki, statik analizin vaxtaşırı deyil, müntəzəm olaraq istifadə edilməsi lazımdır. Məqalələrimiz PVS-Studio analizatorunun imkanlarını yaxşı göstərir, lakin bunun kod keyfiyyətinin yaxşılaşdırılması və səhvlərin düzəldilməsi xərclərinin azaldılması ilə heç bir əlaqəsi yoxdur. Statik kod analizatorundan müntəzəm istifadə edin!
- Biz mövcud diaqnostikanı yekunlaşdırır və təkmilləşdiririk. Buna görə də, analizator əvvəlki skanlar zamanı fərq etmədiyi səhvləri müəyyən edə bilər.
- PVS-Studio-da 2 il əvvəl mövcud olmayan yeni diaqnostikalar meydana çıxdı. PVS-Studio-nun inkişafını aydın şəkildə göstərmək üçün onları ayrıca bölmədə vurğulamaq qərarına gəldim.
2 il əvvəl mövcud olan diaqnostika ilə müəyyən edilmiş qüsurlar
Fraqment N1: Kopyala-Yapışdır
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 xəbərdarlığı:
Adın "avx512.mask.permvar." alt sətri ilə başladığı iki dəfə yoxlanılır. İkinci yoxlamada açıq-aydın başqa bir şey yazmaq istədilər, lakin kopyalanan mətni düzəltməyi unutdular.
Fraqment N2: Yazı səhvi
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;
....
}
Xəbərdarlıq PVS-Studio: V501 '|' solunda və sağında eyni 'CXNameRange_WantQualifier' alt ifadələri var. operator. Cindex.cpp 7245
Yazı səhvinə görə eyni adlı sabit iki dəfə istifadə olunur CXNameRange_WantQualifier.
Fraqment N3: Operatorun üstünlüyü ilə qarışıqlıq
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}
PVS-Studio xəbərdarlığı:
Məncə, bu, çox gözəl səhvdir. Bəli, gözəllik haqqında qəribə fikirlərim olduğunu bilirəm :).
İndi, görə
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Praktik nöqteyi-nəzərdən belə bir vəziyyətin mənası yoxdur, çünki onu aşağıdakılara endirmək olar:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Bu açıq səhvdir. Çox güman ki, 0/1-i dəyişənlə müqayisə etmək istəyirdilər indeks. Kodu düzəltmək üçün üçlü operatorun ətrafına mötərizələr əlavə etməlisiniz:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Yeri gəlmişkən, üçlü operator çox təhlükəlidir və məntiqi səhvlərə səbəb olur. Bununla çox diqqətli olun və mötərizədə xəsislik etməyin. Bu mövzuya daha ətraflı baxdım
Fraqment N4, N5: Null göstərici
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 xəbərdarlığı:
Əgər göstərici LHS sıfırdır, xəbərdarlıq edilməlidir. Bununla belə, əvəzində bu eyni null göstəriciyə istinad ediləcək: LHS->getAsString().
Bu, heç kim onları sınaqdan keçirmədiyi üçün xətanın səhv idarəedicisində gizləndiyi çox tipik bir vəziyyətdir. Statik analizatorlar nə qədər tez-tez istifadə olunmasından asılı olmayaraq bütün əlçatan kodları yoxlayır. Bu, statik analizin digər sınaq və xətalardan qorunma üsullarını necə tamamladığının çox yaxşı nümunəsidir.
Oxşar göstərici ilə işləmə xətası RHS yalnız aşağıdakı kodda icazə verilir: V522 [CWE-476] 'RHS' null göstəricisinə istinadın ləğvi baş verə bilər. TGParser.cpp 2186
Fraqment N6: Hərəkət etdikdən sonra göstəricidən istifadə
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 Xəbərdarlığı: V522 [CWE-476] 'ProgClone' null göstəricisinə istinadın ləğvi baş verə bilər. Yanlış tərtib.cpp 601
Başlanğıcda ağıllı göstərici ProgClone obyektə sahib olmağı dayandırır:
BD.setNewProgram(std::move(ProgClone));
Əslində indi ProgClone null göstəricidir. Buna görə də, sıfır göstəriciyə istinad yalnız aşağıda baş verməlidir:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Ancaq əslində bu baş verməyəcək! Diqqət yetirin ki, döngə əslində icra olunmur.
Konteynerin başında Səhv tərtib edilmiş funksiyalar təmizləndi:
MiscompiledFunctions.clear();
Sonra, bu qabın ölçüsü döngə vəziyyətində istifadə olunur:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Döngənin başlamadığını görmək asandır. Düşünürəm ki, bu da bir səhvdir və kodu başqa cür yazmaq lazımdır.
Deyəsən, o məşhur səhv pariteti ilə qarşılaşdıq! Bir səhv digərini maskalayır :).
Fraqment N7: Hərəkət etdikdən sonra göstəricidən istifadə
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 xəbərdarlığı: V522 [CWE-476] "Sınaq" null göstəricisinə istinadın ləğvi baş verə bilər. Yanlış tərtib.cpp 709
Yenə eyni vəziyyət. Əvvəlcə obyektin məzmunu köçürülür, sonra isə heç nə olmamış kimi istifadə olunur. C++ dilində hərəkət semantikası yarandıqdan sonra bu vəziyyəti daha tez-tez proqram kodunda görürəm. Buna görə C++ dilini sevirəm! Öz ayağını vurmağın getdikcə daha çox yeni yolları var. PVS-Studio analizatoru həmişə işləyəcək :).
Fraqment N8: Boş göstərici
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 xəbərdarlığı: V522 [CWE-476] "Növ" null göstəricisinə istinadın ləğvi baş verə bilər. PrettyFunctionDumper.cpp 233
Səhv işləyicilərinə əlavə olaraq, çap funksiyalarının sazlanması adətən sınaqdan keçirilmir. Qarşımızda belə bir iş var. Funksiya problemlərini həll etmək əvəzinə onu düzəltməyə məcbur olacaq istifadəçini gözləyir.
Düzgün:
if (Type)
Type->dump(*this);
else
Printer << "<unknown-type>";
Fraqment N9: Boş göstərici
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 xəbərdarlığı: V522 [CWE-476] "Ty" null göstəricisinə istinadın ləğvi baş verə bilər. SearchableTableEmitter.cpp 614
Düşünürəm ki, hər şey aydındır və izahat tələb etmir.
Fraqment N10: Yazı səhvi
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 xəbərdarlığı:
Özünə dəyişən təyin etməyin mənası yoxdur. Çox güman ki, yazmaq istəyirdilər:
Identifier->Type = Question->Type;
Fraqment N11: Şübhəli qırılma
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 xəbərdarlığı:
Başlanğıcda çox şübhəli bir operator var sındırmaq. Buraya başqa bir şey yazmağı unutmusan?
Fraqment N12: İstinad ləğv edildikdən sonra göstərici yoxlanılır
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 xəbərdarlığı:
İndeks Callee başlanğıcda funksiyanın çağırıldığı anda istinad edilir getTTI.
Və sonra belə çıxır ki, bu göstərici bərabərlik üçün yoxlanılmalıdır nullptr:
if (!Callee || Callee->isDeclaration())
Amma artıq gecdir...
Fragment N13 - N...: İstinad ləğv edildikdən sonra göstərici yoxlanılır
Əvvəlki kod fraqmentində müzakirə olunan vəziyyət unikal deyil. Burada görünür:
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 xəbərdarlığı: V595 [CWE-476] 'CalleeFn' göstəricisi nullptr ilə təsdiqlənməmişdən əvvəl istifadə edilmişdir. Yoxlama xətləri: 1079, 1081. SimplifyLibCalls.cpp 1079
Və burada:
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 xəbərdarlığı: V595 [CWE-476] 'ND' göstəricisi nullptr ilə təsdiqlənməmişdən əvvəl istifadə edilmişdir. Yoxlama xətləri: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Və burada:
- V595 [CWE-476] 'U' göstəricisi nullptr ilə təsdiqlənməmişdən əvvəl istifadə edilmişdir. Yoxlama xətləri: 404, 407. DWARFormValue.cpp 404
- V595 [CWE-476] 'ND' göstəricisi nullptr ilə təsdiqlənməmişdən əvvəl istifadə edilmişdir. Yoxlama xətləri: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Və sonra V595 nömrəli xəbərdarlıqları öyrənməkdə maraqsız oldum. Ona görə də burada sadalananlardan başqa oxşar xətaların olub-olmadığını bilmirəm. Çox güman ki, var.
Fraqment N17, N18: Şübhəli sürüşmə
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}
PVS-Studio xəbərdarlığı:
Bu, səhv olmaya bilər və kod tam olaraq nəzərdə tutulduğu kimi işləyir. Amma bura çox şübhəli yerdir və yoxlanılmalıdır.
Dəyişən deyək boy 16-ya bərabərdir və sonra kodun müəllifi onu dəyişəndə almağı planlaşdırır NImms dəyər:
1111111111111111111111111111111111111111111111111111111111100000
Ancaq əslində nəticə belə olacaq:
0000000000000000000000000000000011111111111111111111111111100000
Fakt budur ki, bütün hesablamalar 32 bitlik imzasız tipdən istifadə etməklə baş verir. Və yalnız bundan sonra, bu 32-bit imzalanmamış tip dolayısı ilə genişləndiriləcəkdir uint64_t. Bu halda, ən əhəmiyyətli bitlər sıfır olacaq.
Vəziyyəti belə düzəldə bilərsiniz:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Oxşar vəziyyət: V629 [CWE-190] 'Immr << 6' ifadəsini yoxlamağı nəzərdən keçirin. Sonradan 32 bitlik tipə genişlənmə ilə 64 bitlik dəyərin bit dəyişdirilməsi. AArch64AddressingModes.h 269
Fraqment N19: Çatışmayan açar söz daha?
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 xəbərdarlığı:
Burada heç bir səhv yoxdur. O vaxtdan bəri birinci blok if ilə bitir davam etdirmək, onda fərqi yoxdur, açar söz var daha ya yox. İstənilən halda kod eyni işləyəcək. Hələ darıxıb daha kodu daha anlaşılmaz və təhlükəli edir. Əgər gələcəkdə davam etdirmək yox olur, kod tamamilə fərqli işləməyə başlayacaq. Məncə, əlavə etmək daha yaxşıdır daha.
Fraqment N20: Eyni tipli dörd yazı səhvi
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 xəbərdarlıqları:
- V655 [CWE-480] Sətirlər birləşdirildi, lakin istifadə edilmir. 'Nəticə + Name.str()' ifadəsini yoxlamağı nəzərdən keçirin. Symbol.cpp 32
- V655 [CWE-480] Sətirlər birləşdirildi, lakin istifadə edilmir. 'Nəticə + "(ObjC Sinfi)" + Name.str()' ifadəsini yoxlamağı nəzərdən keçirin. Symbol.cpp 35
- V655 [CWE-480] Sətirlər birləşdirildi, lakin istifadə edilmir. 'Nəticə + "(ObjC Class EH)" + Name.str()' ifadəsini yoxlayın. Symbol.cpp 38
- V655 [CWE-480] Sətirlər birləşdirildi, lakin istifadə edilmir. 'Nəticə + "(ObjC IVar)" + Name.str()' ifadəsini yoxlamağı nəzərdən keçirin. Symbol.cpp 41
Təsadüfən += operatoru əvəzinə + operatoru istifadə olunur. Nəticə mənasız dizaynlardır.
Fraqment N21: Müəyyən edilməmiş davranış
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();
}
}
}
Təhlükəli kodu özünüz tapmağa çalışın. Və bu, dərhal cavaba baxmamaq üçün diqqəti yayındırmaq üçün bir şəkildir:
PVS-Studio xəbərdarlığı:
Problem xətti:
FeaturesMap[Op] = FeaturesMap.size();
Əgər element Op tapılmır, sonra xəritədə yeni element yaradılır və orada bu xəritədəki elementlərin sayı yazılır. Funksiyaya zəng edilib-edilməyəcəyi məlum deyil boy yeni element əlavə etməzdən əvvəl və ya sonra.
Fraqment N22-N24: Təkrarlanan tapşırıqlar
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 xəbərdarlığı:
Düşünmürəm ki, burada əsl səhv var. Sadəcə lazımsız təkrarlanan tapşırıq. Amma yenə də kobud səhvdir.
Eynilə:
- V519 [CWE-563] 'B.NDesc' dəyişəninə ardıcıl olaraq iki dəfə qiymətlər təyin edilir. Bəlkə də bu səhvdir. Yoxlama xətləri: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Dəyişənə ardıcıl olaraq iki dəfə qiymətlər təyin edilir. Bəlkə də bu səhvdir. Yoxlama sətirləri: 59, 61. coff2yaml.cpp 61
Fraqment N25-N27: Daha çox təyinat
İndi yenidən təyinatın bir qədər fərqli versiyasına baxaq.
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 xəbərdarlığı: V519 [CWE-563] 'Alignment' dəyişəninə ardıcıl olaraq iki dəfə qiymətlər təyin edilir. Bəlkə də bu səhvdir. Yoxlama xətləri: 1158, 1160. LoadStoreVectorizer.cpp 1160
Bu, məntiqi xəta ehtiva edən çox qəribə koddur. Başlanğıcda, dəyişən Qruplaşma vəziyyətdən asılı olaraq qiymət təyin edilir. Və sonra tapşırıq yenidən baş verir, amma indi heç bir yoxlama olmadan.
Oxşar halları burada görmək olar:
- V519 [CWE-563] 'Effektlər' dəyişəninə ardıcıl olaraq iki dəfə qiymətlər təyin edilir. Bəlkə də bu səhvdir. Yoxlama xətləri: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] 'ExpectNoDerefChunk' dəyişəninə ardıcıl olaraq iki dəfə qiymətlər təyin edilir. Bəlkə də bu səhvdir. Yoxlama xətləri: 4970, 4973. SemaType.cpp 4973
Fraqment N28: Həmişə doğru vəziyyət
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 xəbərdarlığı:
Yoxlamanın mənası yoxdur. Dəyişən nextByte həmişə dəyərinə bərabər deyil 0x90, əvvəlki yoxlamadan irəli gələn. Bu bir növ məntiqi səhvdir.
Fraqment N29 - N...: Həmişə doğru/yalan şərtlər
Analizator bir çox xəbərdarlıq edir ki, bütün vəziyyət (
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 xəbərdarlığı:
0xE sabiti onluqda 14 dəyəridir. İmtahan RegNo == 0xe məntiqli deyil, çünki əgər RegNo > 13, onda funksiya öz icrasını tamamlayacaq.
V547 və V560 identifikatorları ilə bir çox başqa xəbərdarlıqlar var idi, lakin olduğu kimi
Mən sizə bu tətikləri öyrənməyin niyə darıxdırıcı olduğuna dair bir nümunə verəcəyəm. Analizator aşağıdakı kod üçün xəbərdarlıq verməkdə tamamilə haqlıdır. Amma bu səhv deyil.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}
PVS-Studio Xəbərdarlığı: V547 [CWE-570] '!HasError' ifadəsi həmişə yanlışdır. UnwrappedLineParser.cpp 1635
Fraqment N30: Şübhəli qayıdış
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 xəbərdarlığı:
Bu ya səhvdir, ya da kodu oxuyan proqramçılara nəyisə izah etmək üçün nəzərdə tutulmuş xüsusi texnikadır. Bu dizayn mənə heç nə izah etmir və çox şübhəli görünür. Belə yazmasa yaxşı olar :).
Yorğun? Sonra çay və ya qəhvə hazırlamaq vaxtıdır.
Yeni diaqnostika ilə müəyyən edilmiş qüsurlar
Məncə köhnə diaqnostikanın 30 aktivləşdirilməsi kifayətdir. İndi analizatorda sonra ortaya çıxan yeni diaqnostika ilə hansı maraqlı şeylərin tapıla biləcəyinə baxaq
Fraqment N31: Əlçatmaz kod
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 xəbərdarlığı:
Gördüyünüz kimi, operatorun hər iki filialı if operatora zənglə başa çatır qayıtmaq. Buna görə konteyner CtorDtorsByPriority heç vaxt təmizlənməyəcək.
Fraqment N32: Əlçatmaz kod
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 xəbərdarlığı: V779 [CWE-561] Əlçatmaz kod aşkarlandı. Mümkündür ki, xəta mövcuddur. LLparser.cpp 835
Maraqlı vəziyyət. Əvvəlcə bu yerə baxaq:
return ParseTypeIdEntry(SummaryID);
break;
İlk baxışdan elə görünür ki, burada heç bir səhv yoxdur. Operator kimi görünür sındırmaq burada əlavə var və siz onu sadəcə silə bilərsiniz. Ancaq hər şey o qədər də sadə deyil.
Analizator xətlərdə xəbərdarlıq edir:
Lex.setIgnoreColonInIdentifiers(false);
return false;
Və həqiqətən, bu kod əlçatmazdır. Bütün hallarda keçid operatorun zəngi ilə başa çatır qayıtmaq. Və indi mənasız tək sındırmaq o qədər də zərərsiz görünmür! Bəlkə də budaqlardan biri ilə bitməlidir sındırmaq, yox qayıtmaq?
Fraqment N33: Yüksək bitlərin təsadüfi sıfırlanması
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 xəbərdarlığı:
Qeyd edək ki, funksiya getStubAlignment növü qaytarır imzasız. Funksiyanın 8 dəyərini qaytardığını fərz edərək ifadənin qiymətini hesablayaq:
~(getStubAlignment() - 1)
~(8u-1)
0xFFFFFFFF8u
İndi dəyişənə diqqət yetirin DataSize 64 bitlik imzasız tipə malikdir. Məlum oldu ki, DataSize & 0xFFFFFFFF8u əməliyyatını yerinə yetirərkən, bütün otuz iki yüksək səviyyəli bit sıfıra sıfırlanacaq. Çox güman ki, proqramçının istədiyi bu deyil. Hesablamaq istədiyindən şübhələnirəm: DataSize & 0xFFFFFFFFFFFFFF8u.
Xətanı düzəltmək üçün bunu yazmalısınız:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Və ya belə:
DataSize &= ~(getStubAlignment() - 1ULL);
Fraqment N34: Uğursuz açıq tipli yayım
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 xəbərdarlığı:
Tip dəyişənlərini çoxaldan zaman daşqınların qarşısını almaq üçün açıq tipli tökmə istifadə olunur int. Bununla belə, burada açıq tipli tökmə daşqınlardan qorunmur. Əvvəlcə dəyişənlər vurulacaq və yalnız bundan sonra vurmanın 32 bitlik nəticəsi tipə qədər genişləndiriləcək.
Fraqment N35: Kopyalama-Yapışdırmaq uğursuz oldu
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;
}
....
}
Bu yeni maraqlı diaqnostika kod parçasının kopyalandığı və içindəki bəzi adların dəyişdirilməyə başladığı, lakin bir yerdə onu düzəltmədiyi vəziyyətləri müəyyən edir.
Nəzərə alın ki, ikinci blokda onlar dəyişib Op0 haqqında Op1. Amma bir yerdə onu düzəltmədilər. Çox güman ki, belə yazılmalı idi:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}
Fraqment N36: Dəyişən Qarışıqlıq
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};
PVS-Studio xəbərdarlığı:
Funksiya arqumentlərinə sinif üzvləri ilə eyni ad vermək çox təhlükəlidir. Qarışıq olmaq çox asandır. Qarşımızda belə bir iş var. Bu ifadənin mənası yoxdur:
Mode &= Mask;
Funksiya arqumenti dəyişir. Hamısı budur. Bu arqument artıq istifadə edilmir. Çox güman ki, bunu belə yazmalı idiniz:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};
Fraqment N37: Dəyişən Qarışıqlıq
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;
}
Xəbərdarlıq PVS-Studio: V1001 [CWE-563] 'Ölçü' dəyişəni təyin edilir, lakin funksiyanın sonunda istifadə edilmir. Object.cpp 424
Vəziyyət əvvəlki vəziyyətə bənzəyir. Yazılmalıdır:
this->Size += this->EntrySize;
Fraqment N38-N47: Onlar indeksi yoxlamağı unutdular
Əvvəllər diaqnostik tetikleme nümunələrinə baxdıq
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 xəbərdarlığı: V1004 [CWE-476] "Ptr" göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edilib. Yoxlama xətləri: 729, 738. TargetTransformInfoImpl.h 738
Dəyişən Ptr bərabər ola bilər nullptr, çeklə sübut olunduğu kimi:
if (Ptr != nullptr)
Bununla belə, bu göstəricinin altında ilkin yoxlama aparılmadan istinad edilir:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Başqa bir oxşar halı nəzərdən keçirək.
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 xəbərdarlığı: V1004 [CWE-476] 'FD' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 3228, 3231. CGDebugInfo.cpp 3231
İşarəyə diqqət yetirin FD. Əminəm ki, problem aydın görünür və heç bir xüsusi izahat tələb olunmur.
Və daha çox:
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 xəbərdarlığı: V1004 [CWE-476] 'PtrTy' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 960, 965. InterleavedLoadCombinePass.cpp 965
Özünüzü bu cür səhvlərdən necə qorumalısınız? Code-Review-də daha diqqətli olun və kodunuzu müntəzəm olaraq yoxlamaq üçün PVS-Studio statik analizatorundan istifadə edin.
Bu tip səhvləri olan digər kod fraqmentlərinə istinad etməyin mənası yoxdur. Məqalədə yalnız xəbərdarlıqların siyahısını buraxacağam:
- V1004 [CWE-476] 'Expr' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] 'PI' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] 'StatepointCall' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] 'RV' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] 'CalleeFn' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] 'TC' göstəricisi nullptr ilə təsdiqləndikdən sonra etibarsız şəkildə istifadə edildi. Yoxlama xətləri: 1819, 1824. Driver.cpp 1824
Fraqment N48-N60: Kritik deyil, lakin qüsur (mümkün yaddaş sızması)
std::unique_ptr<IRMutator> createISelMutator() {
....
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}
PVS-Studio xəbərdarlığı:
Konteynerin sonuna element əlavə etmək kimi std::vektor > sadəcə yaza bilməzsən xxx.push_back(yeni X), çünki heç bir gizli çevrilmə yoxdur X* в std::unique_ptr.
Ümumi bir həll yazmaqdır xxx.emplace_back(yeni X)tərtib etdiyi üçün: metod emplace_back elementi birbaşa öz arqumentlərindən qurur və buna görə də açıq konstruktorlardan istifadə edə bilər.
Bu təhlükəsiz deyil. Vektor doludursa, yaddaş yenidən ayrılır. Yaddaşın yenidən bölüşdürülməsi əməliyyatı uğursuz ola bilər, nəticədə istisna atılır std :: bad_alloc. Bu halda göstərici itiriləcək və yaradılmış obyekt heç vaxt silinməyəcək.
Təhlükəsiz bir həll yaratmaqdır unikal_ptrvektor yaddaşı yenidən bölüşdürməyə cəhd etməzdən əvvəl göstəriciyə sahib olacaq:
xxx.push_back(std::unique_ptr<X>(new X))
C++14-dən bəri siz 'std::make_unique' istifadə edə bilərsiniz:
xxx.push_back(std::make_unique<X>())
Bu tip qüsur LLVM üçün kritik deyil. Əgər yaddaş ayrıla bilmirsə, kompilyator sadəcə dayanacaq. Ancaq uzun tətbiqlər üçün
Beləliklə, bu kod LLVM üçün praktiki təhlükə yaratmasa da, bu səhv nümunəsi və PVS-Studio analizatorunun onu müəyyən etməyi öyrənməsi haqqında danışmağı faydalı hesab etdim.
Bu tip digər xəbərdarlıqlar:
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Keçidlər' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. PassManager.h 546
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'AAs' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. AliasAnalysis.h 324
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Entries' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'AllEdges' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. CFGMST.h 268
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'VMaps' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Records' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. FDRLogBuilder.h 30
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'PendingSubmodules' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. ModuleMap.cpp 810
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Obyektlər' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. DebugMap.cpp 88
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Strategiyalar' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 685
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 686
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 688
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 689
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 690
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 691
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 692
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 693
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Modifiers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. llvm-stress.cpp 694
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Operands' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Stash' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] Sahibsiz göstərici 'emplace_back' metodu ilə 'Matchers' konteynerinə əlavə edilir. İstisna halında yaddaş sızması baş verəcək. GlobalISelEmitter.cpp 2702
Nəticə
Ümumilikdə 60 xəbərdarlıq etdim, sonra dayandırdım. PVS-Studio analizatorunun LLVM-də aşkar etdiyi başqa qüsurlar varmı? Bəli, məndə var. Ancaq məqalənin kod fraqmentlərini yazarkən axşam, daha doğrusu, gecə idi və qərara gəldim ki, onu gün adlandırmağın vaxtıdır.
Ümid edirəm ki, siz bunu maraqlı tapdınız və PVS-Studio analizatorunu sınamaq istəyəcəksiniz.
Siz analizatoru yükləyə və mina tarama gəmisinin açarını əldə edə bilərsiniz
Ən əsası, müntəzəm olaraq statik analizdən istifadə edin. Birdəfəlik yoxlamalar, statik analiz metodologiyasını populyarlaşdırmaq üçün tərəfimizdən həyata keçirilən və PVS-Studio normal ssenari deyil.
Kodunuzun keyfiyyətini və etibarlılığını artırmaqda uğurlar!
Bu məqaləni ingilisdilli auditoriya ilə bölüşmək istəyirsinizsə, tərcümə linkindən istifadə edin: Andrey Karpov.
Mənbə: www.habr.com