Zaidi ya miaka miwili imepita tangu ukaguzi wa mwisho wa msimbo wa mradi wa LLVM kwa kutumia kichanganuzi chetu cha PVS-Studio. Hebu tuhakikishe kuwa kichanganuzi cha PVS-Studio bado ni zana inayoongoza ya kutambua makosa na udhaifu unaowezekana. Ili kufanya hivyo, tutaangalia na kupata makosa mapya katika toleo la LLVM 8.0.0.
Makala ya kuandikwa
Kusema kweli, sikutaka kuandika makala hii. Haifurahishi kuandika juu ya mradi ambao tayari tumeangalia mara kadhaa (
Kila wakati toleo jipya la LLVM linatolewa au kusasishwa
Tazama, toleo jipya la Clang Static Analyzer limejifunza kupata makosa mapya! Inaonekana kwangu kwamba umuhimu wa kutumia PVS-Studio unapungua. Clang hupata makosa zaidi kuliko hapo awali na anapata uwezo wa PVS-Studio. Una maoni gani kuhusu hili?
Kwa hili kila wakati nataka kujibu kitu kama:
Hatukai bila kazi pia! Tumeboresha kwa kiasi kikubwa uwezo wa kichanganuzi cha PVS-Studio. Kwa hivyo usijali, tunaendelea kuongoza kama hapo awali.
Kwa bahati mbaya, hii ni jibu mbaya. Hakuna uthibitisho ndani yake. Na ndiyo sababu ninaandika makala hii sasa. Kwa hivyo, mradi wa LLVM umeangaliwa tena na makosa kadhaa yamepatikana ndani yake. Sasa nitaonyesha zile ambazo zilionekana kunivutia. Clang Static Analyzer haiwezi kupata makosa haya (au ni ngumu sana kufanya hivyo kwa msaada wake). Lakini tunaweza. Zaidi ya hayo, nilipata na kuandika makosa haya yote kwa jioni moja.
Lakini kuandika makala hiyo kulichukua wiki kadhaa. Sikuweza kujiletea kuweka haya yote kwenye maandishi :).
Kwa njia, ikiwa una nia ya ni teknolojia gani zinazotumiwa katika analyzer ya PVS-Studio kutambua makosa na udhaifu unaowezekana, basi ninapendekeza kufahamiana na hili.
Utambuzi mpya na wa zamani
Kama ilivyoonyeshwa tayari, kama miaka miwili iliyopita mradi wa LLVM ulikaguliwa tena, na makosa yaliyopatikana yalisahihishwa. Sasa makala hii itawasilisha kundi jipya la makosa. Kwa nini mende mpya zilipatikana? Kuna sababu 3 za hii:
- Mradi wa LLVM unabadilika, kubadilisha msimbo wa zamani na kuongeza msimbo mpya. Kwa kawaida, kuna makosa mapya katika kanuni iliyorekebishwa na iliyoandikwa. Hii inaonyesha wazi kwamba uchambuzi wa tuli unapaswa kutumika mara kwa mara, na si mara kwa mara. Makala yetu yanaonyesha vizuri uwezo wa PVS-Studio analyzer, lakini hii haina uhusiano wowote na kuboresha ubora wa kanuni na kupunguza gharama ya kurekebisha makosa. Tumia kichanganuzi cha nambari tuli mara kwa mara!
- Tunakamilisha na kuboresha uchunguzi uliopo. Kwa hiyo, analyzer inaweza kutambua makosa ambayo haikuona wakati wa scans zilizopita.
- Uchunguzi mpya umeonekana katika PVS-Studio ambayo haikuwepo miaka 2 iliyopita. Niliamua kuwaangazia katika sehemu tofauti ili kuonyesha wazi maendeleo ya PVS-Studio.
Kasoro zilizotambuliwa na uchunguzi uliokuwepo miaka 2 iliyopita
Kipande N1: Nakili-Bandika
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
....
}
Onyo la PVS-Studio:
Imeangaliwa mara mbili kwamba jina linaanza na kamba ndogo "avx512.mask.permvar.". Katika hundi ya pili, ni wazi walitaka kuandika kitu kingine, lakini walisahau kusahihisha maandishi yaliyonakiliwa.
Kipande N2: Chapa
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;
....
}
Onyo la PVS-Studio: V501 Kuna vijisemo vidogo vinavyofanana 'CXNameRange_WantQualifier' upande wa kushoto na kulia wa '|' mwendeshaji. CIndex.cpp 7245
Kwa sababu ya kosa la kuandika, ile ile inayoitwa mara kwa mara hutumiwa mara mbili CXNameRange_WantQualifier.
Sehemu N3: Kuchanganyikiwa na utangulizi wa waendeshaji
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}
Onyo la PVS-Studio:
Kwa maoni yangu, hii ni kosa nzuri sana. Ndiyo, najua nina mawazo ya ajabu kuhusu uzuri :).
Sasa, kulingana na
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Kwa mtazamo wa vitendo, hali kama hiyo haina maana, kwani inaweza kupunguzwa kuwa:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Hili ni kosa la wazi. Uwezekano mkubwa zaidi, walitaka kulinganisha 0/1 na kutofautisha index. Ili kurekebisha msimbo unahitaji kuongeza mabano karibu na opereta wa tatu:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Kwa njia, operator wa ternary ni hatari sana na husababisha makosa ya kimantiki. Kuwa mwangalifu nayo sana na usiwe mchoyo wa mabano. Niliangalia mada hii kwa undani zaidi
Kipande N4, N5: Kielekezi tupu
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;
}
....
}
Onyo la PVS-Studio:
Ikiwa pointer LHS ni null, onyo itolewe. Walakini, badala yake, kiashiria hiki hiki kisicho na maana kitatolewa: LHS->getAsString().
Hii ni hali ya kawaida sana wakati kosa limefichwa kwenye kidhibiti makosa, kwani hakuna mtu anayezijaribu. Wachanganuzi tuli huangalia nambari zote zinazoweza kufikiwa, haijalishi inatumiwa mara ngapi. Huu ni mfano mzuri sana wa jinsi uchanganuzi tuli hukamilisha mbinu zingine za majaribio na ulinzi wa makosa.
Hitilafu sawa ya kushughulikia kielekezi RHS inaruhusiwa katika msimbo ulio hapa chini: V522 [CWE-476] Urejeleaji wa kiashirio batili 'RHS' huenda ukafanyika. TGParser.cpp 2186
Fragment N6: Kutumia pointer baada ya kusonga
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);
}
....
}
Onyo la PVS-Studio: V522 [CWE-476] Urejeleaji wa kielekezi batili 'ProgClone' huenda ukafanyika. Kukosea.cpp 601
Hapo mwanzo kielekezi mahiri ProgClone huacha kumiliki kitu:
BD.setNewProgram(std::move(ProgClone));
Kwa kweli, sasa ProgClone ni null pointer. Kwa hivyo, urejeleaji wa null pointer unapaswa kutokea hapa chini:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Lakini, kwa kweli, hii haitatokea! Kumbuka kuwa kitanzi hakijatekelezwa.
Mwanzoni mwa chombo MicompiledFunctions imefutwa:
MiscompiledFunctions.clear();
Ifuatayo, saizi ya chombo hiki hutumiwa katika hali ya kitanzi:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Ni rahisi kuona kwamba kitanzi hakianza. Nadhani hii pia ni mdudu na nambari inapaswa kuandikwa tofauti.
Inaonekana kwamba tumekutana na usawa huo maarufu wa makosa! Kosa moja hufunika lingine :).
Fragment N7: Kutumia pointer baada ya kusonga
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;
}
....
}
Onyo la PVS-Studio: V522 [CWE-476] Kuacha kurejelea kwa 'Jaribio' la kiashiria batili kunaweza kufanyika. Kukosea.cpp 709
Hali hiyo hiyo tena. Mara ya kwanza, yaliyomo kwenye kitu huhamishwa, na kisha hutumiwa kana kwamba hakuna kitu kilichotokea. Ninaona hali hii mara nyingi zaidi katika nambari ya programu baada ya semantiki za harakati kuonekana katika C++. Hii ndiyo sababu ninapenda lugha ya C++! Kuna njia mpya zaidi za kufyatua mguu wako mwenyewe. Kichambuzi cha PVS-Studio kitakuwa na kazi kila wakati :).
Kipande N8: Kielekezi tupu
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);
}
Onyo la PVS-Studio: V522 [CWE-476] Urejeleaji wa 'Aina' ya kiashirio batili huenda ukafanyika. PrettyFunctionDumper.cpp 233
Kando na vidhibiti vya hitilafu, utatuzi wa utendakazi wa kuchapisha kwa kawaida haujaribiwi. Tuna kesi kama hiyo mbele yetu. Kazi inasubiri mtumiaji, ambaye, badala ya kutatua matatizo yake, atalazimika kurekebisha.
Haki:
if (Type)
Type->dump(*this);
else
Printer << "<unknown-type>";
Kipande N9: Kielekezi tupu
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());
....
}
Onyo la PVS-Studio: V522 [CWE-476] Urejeleaji wa kielekezi batili 'Ty' huenda ukafanyika. InatafutwaTableEmitter.cpp 614
Nadhani kila kitu kiko wazi na haihitaji maelezo.
Kipande N10: Chapa
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;
}
Onyo la PVS-Studio:
Hakuna maana katika kugawa kigezo yenyewe. Uwezekano mkubwa zaidi walitaka kuandika:
Identifier->Type = Question->Type;
Kipande N11: Mapumziko ya kutiliwa shaka
void SystemZOperand::print(raw_ostream &OS) const {
switch (Kind) {
break;
case KindToken:
OS << "Token:" << getToken();
break;
case KindReg:
OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
break;
....
}
Onyo la PVS-Studio:
Kuna operator wa shaka sana mwanzoni kuvunja. Je, umesahau kuandika kitu kingine hapa?
Kipande N12: Kuangalia pointer baada ya kutenganisha
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");
....
}
Onyo la PVS-Studio:
Pointer Calee mwanzoni huahirishwa wakati kazi inaitwa pataTTI.
Na kisha inageuka kuwa pointer hii inapaswa kuangaliwa kwa usawa nullptr:
if (!Callee || Callee->isDeclaration())
Lakini ni kuchelewa sana ...
Kipande N13 - N...: Kuangalia kielekezi baada ya kutenganisha
Hali iliyojadiliwa katika kipande cha msimbo uliopita si ya kipekee. Inaonekana hapa:
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()) { // <=
....
}
Onyo la PVS-Studio: V595 [CWE-476] Kielekezi cha 'CalleeFn' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 1079, 1081. SimplifyLibCalls.cpp 1079
Na hapa:
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()); // <=
....
}
Onyo la PVS-Studio: V595 [CWE-476] Kielekezi cha 'ND' kilitumiwa kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Na hapa:
- V595 [CWE-476] Kielekezi cha 'U' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 404, 407. DWARFormValue.cpp 404
- V595 [CWE-476] Kielekezi cha 'ND' kilitumika kabla ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Na kisha nikawa sipendi kusoma maonyo na nambari V595. Kwa hivyo sijui ikiwa kuna makosa zaidi sawa kando na yale yaliyoorodheshwa hapa. Uwezekano mkubwa zaidi kuna.
Kipande N17, N18: Mabadiliko ya kutiliwa shaka
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}
Onyo la PVS-Studio:
Huenda isiwe mdudu na msimbo hufanya kazi kama ilivyokusudiwa. Lakini hii ni wazi mahali pa kutiliwa shaka sana na inahitaji kuangaliwa.
Hebu sema kutofautiana ukubwa ni sawa na 16, na kisha mwandishi wa kanuni alipanga kuipata kwa kutofautiana NImms maana:
1111111111111111111111111111111111111111111111111111111111100000
Walakini, kwa kweli matokeo yatakuwa:
0000000000000000000000000000000011111111111111111111111111100000
Ukweli ni kwamba mahesabu yote hutokea kwa kutumia aina ya 32-bit isiyosajiliwa. Na kisha tu, aina hii ya 32-bit ambayo haijatiwa saini itapanuliwa kikamilifu uint64_t. Katika kesi hii, bits muhimu zaidi zitakuwa sifuri.
Unaweza kurekebisha hali kama hii:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Hali sawa: V629 [CWE-190] Fikiria kukagua usemi wa 'Immr << 6'. Kubadilisha kidogo kwa thamani ya 32-bit na upanuzi unaofuata hadi aina ya 64-bit. AArch64AddressingModes.h 269
Kipande N19: Neno muhimu halipo mwingine?
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");
}
....
}
Onyo la PVS-Studio:
Hakuna makosa hapa. Tangu wakati huo-block ya kwanza if inaisha na kuendelea, basi haijalishi, kuna neno kuu mwingine au siyo. Kwa njia yoyote nambari itafanya kazi sawa. Bado amekosa mwingine hufanya msimbo kuwa wazi zaidi na hatari. Ikiwa katika siku zijazo kuendelea kutoweka, msimbo utaanza kufanya kazi tofauti kabisa. Kwa maoni yangu ni bora kuongeza mwingine.
Kipande N20: Chapa nne za aina moja
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;
}
Maonyo ya PVS-Studio:
- V655 [CWE-480] Mifuatano iliunganishwa lakini haitumiki. Fikiria kukagua usemi wa 'Result + Name.str()'. Alama.cpp 32
- V655 [CWE-480] Mifuatano iliunganishwa lakini haitumiki. Fikiria kukagua usemi wa 'Result + "(ObjC Class)" + Name.str()'. Alama.cpp 35
- V655 [CWE-480] Mifuatano iliunganishwa lakini haitumiki. Fikiria kukagua usemi wa 'Result + "(ObjC Class EH) " + Name.str()'. Alama.cpp 38
- V655 [CWE-480] Mifuatano iliunganishwa lakini haitumiki. Fikiria kukagua usemi wa 'Result + "(ObjC IVar)" + Name.str()'. Alama.cpp 41
Kwa bahati mbaya, opereta + hutumiwa badala ya opereta +=. Matokeo yake ni miundo ambayo haina maana.
Sehemu N21: Tabia isiyobainishwa
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();
}
}
}
Jaribu kupata nambari hatari mwenyewe. Na hii ni picha ya kuvuruga umakini ili usiangalie jibu mara moja:
Onyo la PVS-Studio:
Mstari wa tatizo:
FeaturesMap[Op] = FeaturesMap.size();
Ikiwa kipengele Op haipatikani, basi kipengele kipya kinaundwa kwenye ramani na idadi ya vipengele kwenye ramani hii imeandikwa hapo. Haijulikani ikiwa chaguo la kukokotoa litaitwa kawaida kabla au baada ya kuongeza kipengele kipya.
Sehemu N22-N24: Kazi zinazorudiwa
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;
}
....
}
Onyo la PVS-Studio:
Sidhani kama kuna makosa ya kweli hapa. Mgawo wa kurudiwa tu usio wa lazima. Lakini bado ni kosa.
Vile vile:
- V519 [CWE-563] Tofauti ya 'B.NDesc' imegawiwa thamani mara mbili mfululizo. Pengine hili ni kosa. Angalia mistari: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Tofauti hupewa maadili mara mbili mfululizo. Pengine hili ni kosa. Angalia mistari: 59, 61. coff2yaml.cpp 61
Kipande N25-N27: Kazi nyingine tena
Sasa hebu tuangalie toleo tofauti kidogo la ugawaji upya.
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;
....
}
Onyo la PVS-Studio: V519 [CWE-563] Tofauti ya 'Mpangilio' hupewa thamani mara mbili mfululizo. Pengine hili ni kosa. Angalia mistari: 1158, 1160. LoadStoreVectorizer.cpp 1160
Hii ni nambari ya kushangaza sana ambayo inaonekana ina hitilafu ya kimantiki. Mwanzoni, kutofautiana Alignment thamani inatolewa kulingana na hali. Na kisha mgawo hutokea tena, lakini sasa bila hundi yoyote.
Hali zinazofanana zinaweza kuonekana hapa:
- V519 [CWE-563] Tofauti ya 'Athari' hupewa thamani mara mbili mfululizo. Pengine hili ni kosa. Angalia mistari: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Tofauti ya 'ExpectNoDerefChunk' imepewa maadili mara mbili mfululizo. Pengine hili ni kosa. Angalia mistari: 4970, 4973. SemaType.cpp 4973
Sehemu N28: Hali halisi kila wakati
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;
}
....
}
Onyo la PVS-Studio:
Kukagua haina maana. Inaweza kubadilika nextByte daima si sawa na thamani 0x90, ambayo inafuata kutoka kwa hundi iliyotangulia. Hii ni aina fulani ya makosa ya kimantiki.
Fragment N29 - N...: Hali za kweli/sio kweli kila wakati
Mchambuzi hutoa maonyo mengi kwamba hali nzima (
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;
....
}
Onyo la PVS-Studio:
0xE isiyobadilika ni thamani 14 katika desimali. Uchunguzi RegNo == 0xe haina maana kwa sababu kama RegNo > 13, basi kazi itakamilisha utekelezaji wake.
Kulikuwa na maonyo mengine mengi yenye vitambulisho V547 na V560, lakini kama ilivyokuwa
Nitakupa mfano wa kwa nini kusoma vichochezi hivi kunachosha. Kichanganuzi kiko sahihi kabisa katika kutoa onyo kwa nambari ifuatayo. Lakini hili si kosa.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}
Onyo la PVS-Studio: V547 [CWE-570] Maneno '!HasError' huwa si ya kweli. UnwrappedLineParser.cpp 1635
Sehemu N30: ββKurudi kwa kutia shaka
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();
}
....
}
Onyo la PVS-Studio:
Hili ni kosa au mbinu maalum ambayo imekusudiwa kuelezea kitu kwa watengeneza programu wanaosoma nambari. Muundo huu haunielezi chochote na unaonekana kuwa wa kutiliwa shaka sana. Ni bora si kuandika kama hiyo :).
Umechoka? Kisha ni wakati wa kufanya chai au kahawa.
Kasoro zinazotambuliwa na uchunguzi mpya
Nadhani uanzishaji 30 wa utambuzi wa zamani unatosha. Hebu sasa tuone ni mambo gani ya kuvutia yanaweza kupatikana na uchunguzi mpya ambao ulionekana kwenye analyzer baada ya
Sehemu N31: Nambari isiyoweza kufikiwa
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();
}
Onyo la PVS-Studio:
Kama unaweza kuona, matawi yote mawili ya operator if huisha kwa simu kwa opereta kurudi. Ipasavyo, chombo CtorDtorsByPriority haitafutwa kamwe.
Sehemu N32: Nambari isiyoweza kufikiwa
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;
}
Onyo la PVS-Studio: V779 [CWE-561] Nambari ya kuthibitisha isiyoweza kufikiwa imetambuliwa. Inawezekana kuwa kuna kosa. LLParser.cpp 835
Hali ya kuvutia. Hebu tuangalie mahali hapa kwanza:
return ParseTypeIdEntry(SummaryID);
break;
Kwa mtazamo wa kwanza, inaonekana kwamba hakuna kosa hapa. Inaonekana kama operator kuvunja kuna ya ziada hapa, na unaweza kuifuta kwa urahisi. Walakini, sio zote rahisi sana.
Mchambuzi anatoa onyo kwenye mistari:
Lex.setIgnoreColonInIdentifiers(false);
return false;
Na hakika, kanuni hii haipatikani. Kesi zote ndani kubadili huisha kwa simu kutoka kwa opereta kurudi. Na sasa ujinga peke yako kuvunja haionekani kuwa haina madhara! Labda moja ya matawi inapaswa kumaliza na kuvunjasio kwenye kurudi?
Fragment N33: Kuweka upya bila mpangilio kwa biti za juu
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);
....
}
Onyo la PVS-Studio:
Tafadhali kumbuka kuwa kazi getStubAlignment aina ya kurejesha haijasajiliwa. Wacha tuhesabu thamani ya usemi, tukichukulia kuwa chaguo za kukokotoa hurejesha thamani 8:
~(getStubAlignment() - 1)
~(8u-1)
0xFFFFFFFF8u
Sasa taarifa kwamba kutofautiana Ukubwa wa Data ina aina ya 64-bit ambayo haijasainiwa. Inabadilika kuwa wakati wa kufanya kazi ya DataSize & 0xFFFFFFF8u, bits zote thelathini na mbili za juu zitawekwa upya hadi sifuri. Uwezekano mkubwa zaidi, hii sio kile mtayarishaji wa programu alitaka. Ninashuku kuwa alitaka kukokotoa: DataSize & 0xFFFFFFFFFFFFFFF8u.
Ili kurekebisha kosa, unapaswa kuandika hivi:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Au hivyo:
DataSize &= ~(getStubAlignment() - 1ULL);
Kipande N34: Imeshindwa kutuma aina chafu
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);
....
}
Onyo la PVS-Studio:
Utumaji wa aina dhahiri hutumika kuzuia kufurika wakati wa kuzidisha vigeu vya aina int. Walakini, utumaji wa aina dhahiri hapa haulinde dhidi ya kufurika. Kwanza, vigezo vitazidishwa, na kisha tu matokeo ya 32-bit ya kuzidisha yatapanuliwa kwa aina.
Kipande N35: Imeshindwa Kunakili-Bandika
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;
}
....
}
Utambuzi huu mpya unaovutia unabainisha hali ambapo kipande cha msimbo kimenakiliwa na baadhi ya majina yameanza kubadilishwa ndani yake, lakini katika sehemu moja hawajaisahihisha.
Tafadhali kumbuka kuwa katika block ya pili walibadilika Op0 juu ya Op1. Lakini katika sehemu moja hawakurekebisha. Uwezekano mkubwa zaidi, inapaswa kuandikwa kama hii:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}
Kipande N36: Mchanganyiko Unaobadilika
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};
Onyo la PVS-Studio:
Ni hatari sana kutoa hoja za kazi kwa majina sawa na washiriki wa darasa. Ni rahisi sana kuchanganyikiwa. Tuna kesi kama hiyo mbele yetu. Usemi huu hauna maana:
Mode &= Mask;
Hoja ya kukokotoa inabadilika. Ni hayo tu. Hoja hii haitumiki tena. Uwezekano mkubwa zaidi unapaswa kuandika kama hii:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};
Kipande N37: Mchanganyiko Unaobadilika
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;
}
Onyo la PVS-Studio: V1001 [CWE-563] Tofauti ya 'Ukubwa' imetolewa lakini haitumiki kufikia mwisho wa chaguo la kukokotoa. Object.cpp 424
Hali ni sawa na ile iliyopita. Inapaswa kuandikwa:
this->Size += this->EntrySize;
Fragment N38-N47: Walisahau kuangalia index
Hapo awali, tuliangalia mifano ya kuchochea uchunguzi
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()); // <=
....
}
Onyo la PVS-Studio: V1004 [CWE-476] Kielekezi cha 'Ptr' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 729, 738. TargetTransformInfoImpl.h 738
Inaweza kubadilika Ptr inaweza kuwa sawa nullptr, kama inavyothibitishwa na hundi:
if (Ptr != nullptr)
Walakini, chini ya pointer hii imeahirishwa bila ukaguzi wa awali:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Wacha tuchunguze kesi nyingine kama hiyo.
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(); // <=
....
}
Onyo la PVS-Studio: V1004 [CWE-476] Kielekezi cha 'FD' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 3228, 3231. CGDebugInfo.cpp 3231
Makini na ishara FD. Nina hakika shida inaonekana wazi na hakuna maelezo maalum inahitajika.
Na zaidi:
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()); // <=
....
}
Onyo la PVS-Studio: V1004 [CWE-476] Kielekezi cha 'PtrTy' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 960, 965. InterleavedLoadCombinePass.cpp 965
Jinsi ya kujikinga na makosa kama haya? Kuwa mwangalifu zaidi kuhusu Ukaguzi wa Msimbo na utumie kichanganuzi tuli cha PVS-Studio ili kuangalia msimbo wako mara kwa mara.
Hakuna maana katika kutaja vipande vingine vya kanuni na makosa ya aina hii. Nitaacha orodha ya maonyo tu katika kifungu:
- V1004 [CWE-476] Kielekezi cha 'Expr' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Kielekezi cha 'PI' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Kielekezi cha 'StatepointCall' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] Kielekezi cha 'RV' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] Kielekezi cha 'CalleeFn' kilitumika kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] Kielekezi cha 'TC' kilitumiwa kwa njia isiyo salama baada ya kuthibitishwa dhidi ya nullptr. Angalia mistari: 1819, 1824. Driver.cpp 1824
Sehemu N48-N60: Sio muhimu, lakini kasoro (uvujaji wa kumbukumbu unaowezekana)
std::unique_ptr<IRMutator> createISelMutator() {
....
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}
Onyo la PVS-Studio:
Ili kuongeza kipengee hadi mwisho wa chombo kama std::vekta > huwezi kuandika tu xxx.push_back(X mpya), kwa kuwa hakuna ubadilishaji kamili kutoka X* Π² std::kipekee_ptr.
Suluhisho la kawaida ni kuandika xxx.emplace_back(X mpya)kwani inakusanya: mbinu emplace_back huunda kipengele moja kwa moja kutoka kwa hoja zake na kwa hivyo inaweza kutumia wajenzi wazi.
Sio salama. Ikiwa vekta imejaa, basi kumbukumbu inatolewa tena. Operesheni ya uhamishaji wa kumbukumbu inaweza kushindwa, na kusababisha ubaguzi kutupwa std::bad_alloc. Katika kesi hii, pointer itapotea na kitu kilichoundwa hakitafutwa kamwe.
Suluhisho salama ni kuunda kipekee_ptrambayo itamiliki pointer kabla ya vekta kujaribu kuhamisha kumbukumbu tena:
xxx.push_back(std::unique_ptr<X>(new X))
Tangu C++14, unaweza kutumia 'std::make_unique':
xxx.push_back(std::make_unique<X>())
Aina hii ya kasoro sio muhimu kwa LLVM. Ikiwa kumbukumbu haiwezi kugawanywa, mkusanyaji ataacha tu. Hata hivyo, kwa maombi na muda mrefu
Kwa hivyo, ingawa nambari hii haileti tishio la vitendo kwa LLVM, niliona ni muhimu kuzungumza juu ya muundo huu wa makosa na kwamba mchambuzi wa PVS-Studio amejifunza kuitambua.
Maonyo mengine ya aina hii:
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Passes' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. PassManager.h 546
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'AAs' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. LakabuUchambuzi.h 324
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Entries' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye kontena la 'AllEdges' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. CFGMST.h 268
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'VMaps' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Rekodi' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. FDRLogBuilder.h 30
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye chombo cha 'PendingSubmodules' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. ModuliMap.cpp 810
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Objects' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. DebugMap.cpp 88
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye kontena la 'Strategies' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 685
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 686
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 688
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 689
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 690
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 691
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 692
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 693
- V1023 [CWE-460] Kielekezi kisicho na mmiliki kinaongezwa kwenye kontena la 'Modifiers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. llvm-stress.cpp 694
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Operands' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. GlobalISElEmitter.cpp 1911
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Stash' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. GlobalISElEmitter.cpp 2100
- V1023 [CWE-460] Kielekezi bila mmiliki kinaongezwa kwenye chombo cha 'Matchers' kwa mbinu ya 'emplace_back'. Uvujaji wa kumbukumbu utatokea katika kesi ya ubaguzi. GlobalISElEmitter.cpp 2702
Hitimisho
Nilitoa maonyo 60 kwa jumla kisha nikaacha. Kuna kasoro zingine ambazo kichanganuzi cha PVS-Studio hugundua katika LLVM? Ndio ninayo. Hata hivyo, nilipokuwa nikiandika vipande vya msimbo wa makala hiyo, ilikuwa jioni sana, au tuseme hata usiku, na niliamua kuwa ni wakati wa kuiita siku.
Natumai umeipata ya kufurahisha na utataka kujaribu kichanganuzi cha PVS-Studio.
Unaweza kupakua kichanganuzi na kupata ufunguo wa minesweeper
Muhimu zaidi, tumia uchambuzi wa tuli mara kwa mara. Hundi za mara moja, uliofanywa na sisi ili kutangaza mbinu ya uchambuzi wa tuli na PVS-Studio sio hali ya kawaida.
Bahati nzuri katika kuboresha ubora na uaminifu wa nambari yako!
Ikiwa ungependa kushiriki makala hii na hadhira inayozungumza Kiingereza, tafadhali tumia kiungo cha kutafsiri: Andrey Karpov.
Chanzo: mapenzi.com