PVS-Studio analizatorundan istifadə edərək LLVM 8-də səhvlərin tapılması

PVS-Studio analizatorundan istifadə edərək LLVM 8-də səhvlərin tapılması
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 (1, 2, 3). Yeni bir şey haqqında yazmaq daha yaxşıdır, amma seçimim yoxdur.

Hər dəfə LLVM-in yeni versiyası buraxılanda və ya yenilənir Clang Statik Analizator, poçtumuzda aşağıdakı növ suallar alırıq:

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. Qeyd.

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:

  1. 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!
  2. 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.
  3. 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ığı: V501 [CWE-570] '||' solunda və sağında 'Name.startswith("avx512.mask.permvar.")' eyni alt ifadələr var. operator. AutoUpgrade.cpp 73

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ığı: V502 [CWE-783] Ola bilsin ki, '?:' operatoru gözlənildiyindən fərqli şəkildə işləyir. '?:' operatoru '==' operatorundan daha aşağı prioritetə ​​malikdir. PPCTargetTransformInfo.cpp 404

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ə operatorun prioritetləri, ifadə aşağıdakı kimi qiymətləndirilir:

(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 burada, bölməsində "Diqqət edin ?: Operator və Mötərizəyə daxil edin."

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ığı: V522 [CWE-476] "LHS" null göstəricisinə istinadın ləğvi baş verə bilər. TGParser.cpp 2152

Ə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ığı: V570 'Identifier->Type' dəyişəni özünə təyin edilir. FormatTokenLexer.cpp 249

Ö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ığı: V622 [CWE-478] 'keçid' ifadəsini yoxlamağı nəzərdən keçirin. Mümkündür ki, birinci "hal" operatoru yoxdur. SystemZAsmParser.cpp 652

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ığı: V595 [CWE-476] "Zəng edən" göstərici nullptr ilə təsdiqlənməmişdən əvvəl istifadə edilmişdir. Yoxlama xətləri: 172, 174. AMDGPUInline.cpp 172

İ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ığı: V629 [CWE-190] '~(Ölçü - 1) << 1' ifadəsini yoxlayın. Sonradan 32 bitlik tipə genişlənmə ilə 64 bitlik dəyərin bit dəyişdirilməsi. AArch64AddressingModes.h 260

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ığı: V646 [CWE-670] Tətbiqin məntiqini yoxlamağı düşünün. Ola bilsin ki, "else" açar sözünün olmaması. AMDGPUAsmParser.cpp 5655

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 analizatorundan istifadə edərək LLVM 8-də səhvlərin tapılması

PVS-Studio xəbərdarlığı: V708 [CWE-758] Təhlükəli tikinti istifadə olunur: 'FeaturesMap[Op] = FeaturesMap.size()', burada 'FeaturesMap' 'xəritə' sinfinə aiddir. Bu, qeyri-müəyyən davranışa səbəb ola bilər. RISCVCompressInstEmitter.cpp 490

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ığı: V519 [CWE-563] 'NType' 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: 1663, 1664. MachOObjectFile.cpp 1664

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ığı: V547 [CWE-571] 'nextByte != 0x90' ifadəsi həmişə doğrudur. X86DisassemblerDecoder.cpp 379

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 (V547) və ya onun bir hissəsi (V560) həmişə doğru və ya yanlışdır. Çox vaxt bunlar real səhvlər deyil, sadəcə səliqəsiz kod, makro genişlənmənin nəticəsi və s. Bununla belə, zaman-zaman əsl məntiqi səhvlər baş verdiyi üçün bütün bu xəbərdarlıqlara baxmaq məntiqlidir. Məsələn, kodun bu bölməsi şübhəlidir:

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ığı: V560 [CWE-570] Şərti ifadənin bir hissəsi həmişə yanlışdır: RegNo == 0xe. ARMDisassembler.cpp 939

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 V595, bu xəbərdarlıqları öyrənmək mənə maraqlı deyildi. Məqalə yazmaq üçün kifayət qədər materialım olduğu artıq aydın idi :). Buna görə də, PVS-Studio-dan istifadə edərək LLVM-də bu tipli neçə səhvin müəyyən edilə biləcəyi məlum deyil.

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ığı: V612 [CWE-670] Döngü daxilində qeyd-şərtsiz 'qayıt'. R600OptimizeVectorRegisters.cpp 63

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.

PVS-Studio analizatorundan istifadə edərək LLVM 8-də səhvlərin tapılması

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 əvvəlki çeklər. Bu müddət ərzində C++ analizatoruna ümumilikdə 66 ümumi təyinatlı diaqnostika əlavə edildi.

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ığı: V779 [CWE-561] Əlçatmaz kod aşkarlandı. Səhv olması mümkündür. ExecutionUtils.cpp 146

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ığı: V784 Bit maskasının ölçüsü birinci operandın ölçüsündən kiçikdir. Bu, daha yüksək bitlərin itirilməsinə səbəb olacaq. RuntimeDyld.cpp 815

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ığı: V1028 [CWE-190] Mümkün daşqın. 'NumElts * Scale' operatorunun operandlarını nəticəyə deyil, 'size_t' növünə köçürməyi nəzərdən keçirin. X86ISelLowering.h 1577

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. ölçüsü_t.

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;
  }
  ....
}

V778 [CWE-682] İki oxşar kod parçası tapıldı. Ola bilsin ki, bu yazı səhvidir və 'Op1' əvəzinə 'Op0' dəyişəni istifadə edilməlidir. InstCombineCompares.cpp 5507

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ığı: V1001 [CWE-563] 'Rejim' dəyişəni təyin edilib, lakin funksiyanın sonunda istifadə edilmir. SIModeRegister.cpp 48

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 V595. Onun mahiyyəti ondan ibarətdir ki, göstərici başlanğıcda ləğv edilir və yalnız bundan sonra yoxlanılır. Gənc diaqnostika V1004 mənaca əksinədir, həm də çoxlu səhvləri üzə çıxarır. O, göstəricinin əvvəlində yoxlanıldığı və sonra bunu etməyi unutduğu vəziyyətləri müəyyən edir. LLVM daxilində tapılan belə hallara baxaq.

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ığı: 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 58

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 iş vaxtı, yaddaşın ayrılması uğursuz olarsa, bu, sadəcə olaraq dayandırıla bilməz, bu, əsl pis səhv ola bilər.

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 bu səhifə.

Ə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!

PVS-Studio analizatorundan istifadə edərək LLVM 8-də səhvlərin tapılması

Bu məqaləni ingilisdilli auditoriya ilə bölüşmək istəyirsinizsə, tərcümə linkindən istifadə edin: Andrey Karpov. PVS-Studio ilə LLVM 8-də səhvlərin tapılması.

Mənbə: www.habr.com

Добавить комментарий