Tìm lỗi trong LLVM 8 bằng máy phân tích PVS-Studio

Tìm lỗi trong LLVM 8 bằng máy phân tích PVS-Studio
Đã hơn hai năm trôi qua kể từ lần kiểm tra mã cuối cùng của dự án LLVM bằng máy phân tích PVS-Studio của chúng tôi. Hãy đảm bảo rằng máy phân tích PVS-Studio vẫn là công cụ hàng đầu để xác định lỗi và các lỗ hổng tiềm ẩn. Để thực hiện việc này, chúng tôi sẽ kiểm tra và tìm ra các lỗi mới trong bản phát hành LLVM 8.0.0.

Bài viết sắp viết

Thành thật mà nói, tôi không muốn viết bài này. Thật không thú vị khi viết về một dự án mà chúng tôi đã kiểm tra nhiều lần (1, 2, 3). Sẽ tốt hơn nếu viết về điều gì đó mới mẻ, nhưng tôi không có lựa chọn nào khác.

Mỗi khi có phiên bản LLVM mới được phát hành hoặc cập nhật Máy phân tích tĩnh Clang, chúng tôi nhận được các câu hỏi thuộc loại sau trong thư của mình:

Hãy nhìn xem, phiên bản mới của Clang Static Phân tích đã học cách tìm ra các lỗi mới! Đối với tôi, có vẻ như mức độ liên quan của việc sử dụng PVS-Studio đang giảm dần. Clang tìm thấy nhiều lỗi hơn trước và bắt kịp khả năng của PVS-Studio. Bạn nghĩ gì về điều này?

Về vấn đề này tôi luôn muốn trả lời một câu như:

Chúng tôi cũng không ngồi yên! Chúng tôi đã cải thiện đáng kể khả năng của máy phân tích PVS-Studio. Vậy nên đừng lo lắng, chúng ta vẫn tiếp tục dẫn đầu như trước.

Thật không may, đây là một câu trả lời tồi. Không có bằng chứng nào trong đó. Và đó là lý do tại sao bây giờ tôi viết bài này. Vì vậy, dự án LLVM một lần nữa đã được kiểm tra và nhiều lỗi đã được tìm thấy trong đó. Bây giờ tôi sẽ chứng minh những điều mà tôi thấy thú vị. Trình phân tích tĩnh Clang không thể tìm thấy những lỗi này (hoặc cực kỳ bất tiện khi làm như vậy với sự trợ giúp của nó). Nhưng chúng ta có thể. Hơn nữa, tôi đã tìm ra và ghi lại tất cả những lỗi này chỉ trong một buổi tối.

Nhưng việc viết bài phải mất vài tuần. Tôi không thể tự mình viết tất cả những điều này thành văn bản :).

Nhân tiện, nếu bạn quan tâm đến công nghệ nào được sử dụng trong máy phân tích PVS-Studio để xác định lỗi và các lỗ hổng tiềm ẩn, thì tôi khuyên bạn nên làm quen với điều này Ghi chú.

Chẩn đoán mới và cũ

Như đã lưu ý, khoảng hai năm trước, dự án LLVM đã được kiểm tra một lần nữa và các lỗi tìm thấy đã được sửa chữa. Bây giờ bài viết này sẽ trình bày một loạt lỗi mới. Tại sao lỗi mới được tìm thấy? Có 3 lý do cho việc này:

  1. Dự án LLVM đang phát triển, thay đổi mã cũ và thêm mã mới. Đương nhiên, có những lỗi mới trong mã được sửa đổi và viết. Điều này chứng tỏ rõ ràng rằng phân tích tĩnh nên được sử dụng thường xuyên chứ không phải thỉnh thoảng. Các bài viết của chúng tôi thể hiện rõ khả năng của máy phân tích PVS-Studio, nhưng điều này không liên quan gì đến việc cải thiện chất lượng mã và giảm chi phí sửa lỗi. Sử dụng máy phân tích mã tĩnh thường xuyên!
  2. Chúng tôi đang hoàn thiện và cải thiện chẩn đoán hiện có. Do đó, máy phân tích có thể xác định các lỗi mà nó không nhận thấy trong các lần quét trước.
  3. Chẩn đoán mới đã xuất hiện trong PVS-Studio mà cách đây 2 năm không tồn tại. Tôi quyết định nêu bật chúng trong một phần riêng để thể hiện rõ sự phát triển của PVS-Studio.

Các khiếm khuyết được xác định bằng chẩn đoán đã tồn tại 2 năm trước

Đoạn N1: Sao chép-Dán

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

Cảnh báo của PVS-Studio: V501 [CWE-570] Có các biểu thức phụ giống hệt nhau 'Name.startswith("avx512.mask.permvar.")' ở bên trái và bên phải của '||' nhà điều hành. Tự động nâng cấp.cpp 73

Kiểm tra kỹ xem tên có bắt đầu bằng chuỗi con "avx512.mask.permvar." hay không. Trong lần kiểm tra thứ hai, rõ ràng họ muốn viết gì đó khác nhưng lại quên sửa văn bản đã sao chép.

Đoạn N2: Lỗi đánh máy

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

Cảnh báo PVS-Studio: V501 Có các biểu thức phụ giống hệt nhau 'CXNameRange_WantQualifier' ở bên trái và bên phải của '|' nhà điều hành. CIndex.cpp 7245

Do lỗi đánh máy, hằng số có tên giống nhau được sử dụng hai lần CXNameRange_WantQualifier.

Đoạn N3: Nhầm lẫn về độ ưu tiên của toán tử

int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
  ....
  if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
    return 0;
  ....
}

Cảnh báo của PVS-Studio: V502 [CWE-783] Có lẽ toán tử '?:' hoạt động theo cách khác với mong đợi. Toán tử '?:' có mức độ ưu tiên thấp hơn toán tử '=='. PPCTargetTransformInfo.cpp 404

Theo tôi, đây là một sai lầm rất đẹp. Vâng, tôi biết tôi có những ý tưởng kỳ lạ về cái đẹp :).

Bây giờ, theo ưu tiên của nhà điều hành, biểu thức được đánh giá như sau:

(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0

Từ quan điểm thực tế, điều kiện như vậy không có ý nghĩa vì nó có thể được rút gọn thành:

(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())

Đây là một sai lầm rõ ràng. Rất có thể, họ muốn so sánh 0/1 với một biến Chỉ số. Để sửa mã, bạn cần thêm dấu ngoặc đơn xung quanh toán tử ternary:

if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))

Nhân tiện, toán tử bậc ba rất nguy hiểm và gây ra các lỗi logic. Hãy thật cẩn thận với nó và đừng tham lam với dấu ngoặc đơn. Tôi đã xem xét chủ đề này chi tiết hơn đây, trong chương “Hãy cẩn thận với ?: Toán tử và đặt nó trong ngoặc đơn.”

Đoạn N4, N5: Con trỏ null

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

Cảnh báo của PVS-Studio: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'LHS' có thể diễn ra. TGParser.cpp 2152

Nếu con trỏ LHS là null, một cảnh báo sẽ được đưa ra. Tuy nhiên, thay vào đó, con trỏ null này sẽ bị hủy đăng ký: LHS->getAsString().

Đây là một tình huống rất điển hình khi một lỗi được ẩn trong trình xử lý lỗi vì không ai kiểm tra chúng. Máy phân tích tĩnh kiểm tra tất cả các mã có thể truy cập được, bất kể nó được sử dụng thường xuyên như thế nào. Đây là một ví dụ rất hay về cách phân tích tĩnh bổ sung cho các kỹ thuật kiểm tra và bảo vệ lỗi khác.

Lỗi xử lý con trỏ tương tự RHS được phép trong mã ngay bên dưới: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'RHS' có thể diễn ra. TGParser.cpp 2186

Đoạn N6: Sử dụng con trỏ sau khi di chuyển

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

Cảnh báo PVS-Studio: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'ProgClone' có thể diễn ra. Biên dịch sai.cpp 601

Lúc đầu một con trỏ thông minh ProgClone ngừng sở hữu đối tượng:

BD.setNewProgram(std::move(ProgClone));

Trên thực tế, bây giờ ProgClone là một con trỏ null. Do đó, việc hủy bỏ tham chiếu con trỏ null sẽ xảy ra ngay bên dưới:

Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);

Nhưng trên thực tế, điều này sẽ không xảy ra! Lưu ý rằng vòng lặp không thực sự được thực thi.

Ở đầu thùng chứa Hàm biên dịch sai đã xóa:

MiscompiledFunctions.clear();

Tiếp theo, kích thước của vùng chứa này được sử dụng trong điều kiện vòng lặp:

for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {

Thật dễ dàng để thấy rằng vòng lặp không bắt đầu. Tôi nghĩ đây cũng là một lỗi và mã nên được viết khác đi.

Có vẻ như chúng ta đã gặp phải lỗi tương đương nổi tiếng đó! Một sai lầm che giấu một sai lầm khác :).

Đoạn N7: Sử dụng con trỏ sau khi di chuyển

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

Cảnh báo của PVS-Studio: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'Thử nghiệm' có thể diễn ra. Biên dịch sai.cpp 709

Tình huống tương tự một lần nữa. Lúc đầu, nội dung của đối tượng được di chuyển, sau đó nó được sử dụng như không có chuyện gì xảy ra. Tôi thấy tình trạng này ngày càng thường xuyên hơn trong mã chương trình sau khi ngữ nghĩa chuyển động xuất hiện trong C++. Đây là lý do tại sao tôi yêu thích ngôn ngữ C++! Ngày càng có nhiều cách mới để tự bắn đứt chân mình. Máy phân tích PVS-Studio sẽ luôn hoạt động :).

Đoạn N8: Con trỏ null

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

Cảnh báo PVS-Studio: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'Loại' có thể diễn ra. PrettyFunctionDumper.cpp 233

Ngoài các trình xử lý lỗi, chức năng gỡ lỗi in ra thường không được kiểm tra. Chúng tôi vừa có một trường hợp như vậy trước mắt. Chức năng này đang chờ người dùng, thay vì giải quyết vấn đề của mình, họ sẽ buộc phải sửa nó.

Chính xác:

if (Type)
  Type->dump(*this);
else
  Printer << "<unknown-type>";

Đoạn N9: Con trỏ null

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

Cảnh báo của PVS-Studio: V522 [CWE-476] Việc hủy tham chiếu con trỏ null 'Ty' có thể diễn ra. SearchableTableEmitter.cpp 614

Tôi nghĩ mọi thứ đều rõ ràng và không cần giải thích.

Đoạn N10: Lỗi đánh máy

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

Cảnh báo của PVS-Studio: V570 Biến 'Mã định danh->Loại' được gán cho chính nó. FormatTokenLexer.cpp 249

Không có ích gì khi gán một biến cho chính nó. Rất có thể họ muốn viết:

Identifier->Type = Question->Type;

Đoạn N11: Đột phá đáng ngờ

void SystemZOperand::print(raw_ostream &OS) const {
  switch (Kind) {
    break;
  case KindToken:
    OS << "Token:" << getToken();
    break;
  case KindReg:
    OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
    break;
  ....
}

Cảnh báo của PVS-Studio: V622 [CWE-478] Hãy cân nhắc việc kiểm tra câu lệnh 'switch'. Có thể toán tử 'case' đầu tiên bị thiếu. SystemZAsmParser.cpp 652

Có một nhà điều hành rất đáng ngờ ngay từ đầu phá vỡ. Bạn quên viết gì khác ở đây à?

Đoạn N12: Kiểm tra con trỏ sau khi hội thảo

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

Cảnh báo của PVS-Studio: V595 [CWE-476] Con trỏ 'Callee' đã được sử dụng trước khi nó được xác minh dựa trên nullptr. Kiểm tra các dòng: 172, 174. AMDGPUInline.cpp 172

Con trỏ callee lúc đầu bị hủy đăng ký tại thời điểm hàm được gọi nhậnTTI.

Và sau đó hóa ra con trỏ này cần được kiểm tra sự bằng nhau nullptr:

if (!Callee || Callee->isDeclaration())

Nhưng quá trễ rồi…

Đoạn N13 - N...: Kiểm tra con trỏ sau khi hội thảo

Tình huống được thảo luận trong đoạn mã trước không phải là duy nhất. Nó xuất hiện ở đây:

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()) {               // <=
  ....
}

Cảnh báo PVS-Studio: V595 [CWE-476] Con trỏ 'CalleeFn' đã được sử dụng trước khi nó được xác minh dựa trên nullptr. Kiểm tra các dòng: 1079, 1081. SimplifyLibCalls.cpp 1079

Và đây:

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());     // <=
  ....
}

Cảnh báo PVS-Studio: V595 [CWE-476] Con trỏ 'ND' đã được sử dụng trước khi nó được xác minh dựa trên nullptr. Kiểm tra các dòng: 532, 534. SemaTemplateInstantiateDecl.cpp 532

Và đây:

  • V595 [CWE-476] Con trỏ 'U' đã được sử dụng trước khi nó được xác minh dựa trên nullptr. Kiểm tra các dòng: 404, 407. DWARFormValue.cpp 404
  • V595 [CWE-476] Con trỏ 'ND' đã được sử dụng trước khi nó được xác minh dựa trên nullptr. Kiểm tra các dòng: 2149, 2151. SemaTemplateInstantiate.cpp 2149

Và rồi tôi không còn hứng thú nghiên cứu những cảnh báo mang số V595. Vì vậy, tôi không biết liệu có thêm lỗi tương tự nào ngoài những lỗi được liệt kê ở đây hay không. Nhiều khả năng là có.

Đoạn N17, N18: Chuyển dịch đáng ngờ

static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
                                           uint64_t &Encoding) {
  ....
  unsigned Size = RegSize;
  ....
  uint64_t NImms = ~(Size-1) << 1;
  ....
}

Cảnh báo của PVS-Studio: V629 [CWE-190] Hãy cân nhắc việc kiểm tra biểu thức '~(Size - 1) << 1'. Dịch chuyển bit của giá trị 32 bit với sự mở rộng tiếp theo sang loại 64 bit. AArch64AddressingModes.h 260

Nó có thể không phải là lỗi và mã hoạt động chính xác như dự định. Nhưng đây rõ ràng là một nơi rất đáng ngờ và cần phải kiểm tra.

Giả sử biến Kích thước máy bằng 16, và sau đó tác giả của đoạn mã đã lên kế hoạch đưa nó vào một biến NImm Ý nghĩa:

1111111111111111111111111111111111111111111111111111111111100000

Tuy nhiên, trên thực tế kết quả sẽ là:

0000000000000000000000000000000011111111111111111111111111100000

Thực tế là tất cả các phép tính đều diễn ra bằng cách sử dụng loại không dấu 32 bit. Và chỉ khi đó, loại không dấu 32 bit này mới được ngầm mở rộng thành uint64_t. Trong trường hợp này, các bit quan trọng nhất sẽ bằng XNUMX.

Bạn có thể khắc phục tình trạng như thế này:

uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;

Tình huống tương tự: V629 [CWE-190] Hãy cân nhắc việc kiểm tra biểu thức 'Immr << 6'. Dịch chuyển bit của giá trị 32 bit với sự mở rộng tiếp theo sang loại 64 bit. AArch64AddressingModes.h 269

Đoạn N19: Thiếu từ khóa khác?

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

Cảnh báo của PVS-Studio: V646 [CWE-670] Hãy cân nhắc việc kiểm tra logic của ứng dụng. Có thể từ khóa 'else' bị thiếu. AMDGPUAsmParser.cpp 5655

Không có sai lầm ở đây. Kể từ khối then của đầu tiên if kết thúc bằng tiếp tục, thế thì không sao đâu, có từ khóa mà khác hay không. Dù bằng cách nào thì mã sẽ hoạt động giống nhau. Vẫn nhớ khác làm cho mã trở nên không rõ ràng và nguy hiểm hơn. Nếu trong tương lai tiếp tục biến mất, mã sẽ bắt đầu hoạt động hoàn toàn khác. Theo tôi tốt hơn là thêm khác.

Đoạn N20: Bốn lỗi chính tả cùng loại

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

Cảnh báo của PVS-Studio:

  • V655 [CWE-480] Các dây đã được nối nhưng không được sử dụng. Hãy cân nhắc việc kiểm tra biểu thức 'Result + Name.str()'. Symbol.cpp 32
  • V655 [CWE-480] Các dây đã được nối nhưng không được sử dụng. Hãy cân nhắc việc kiểm tra biểu thức 'Result + "(ObjC Class)" + Name.str()'. Symbol.cpp 35
  • V655 [CWE-480] Các dây đã được nối nhưng không được sử dụng. Hãy cân nhắc việc kiểm tra biểu thức 'Result + "(ObjC Class EH) " + Name.str()'. Symbol.cpp 38
  • V655 [CWE-480] Các dây đã được nối nhưng không được sử dụng. Hãy cân nhắc việc kiểm tra biểu thức 'Result + "(ObjC IVar)" + Name.str()'. Symbol.cpp 41

Tình cờ, toán tử + được sử dụng thay vì toán tử +=. Kết quả là những thiết kế không có ý nghĩa.

Đoạn N21: Hành vi không xác định

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();
    }
  }
}

Hãy cố gắng tự mình tìm ra mã nguy hiểm. Và đây là hình ảnh nhằm đánh lạc hướng sự chú ý để không nhìn ngay vào đáp án:

Tìm lỗi trong LLVM 8 bằng máy phân tích PVS-Studio

Cảnh báo của PVS-Studio: V708 [CWE-758] Cấu trúc nguy hiểm được sử dụng: 'FeaturesMap[Op] = FeaturesMap.size()', trong đó 'FeaturesMap' thuộc lớp 'map'. Điều này có thể dẫn đến hành vi không xác định. RISCVCompressInstEmitter.cpp 490

Dòng vấn đề:

FeaturesMap[Op] = FeaturesMap.size();

Nếu phần tử Op không được tìm thấy thì một phần tử mới sẽ được tạo trên bản đồ và số lượng phần tử trong bản đồ này được ghi vào đó. Chỉ là chưa biết liệu hàm này có được gọi hay không kích thước trước hoặc sau khi thêm phần tử mới.

Đoạn N22-N24: Bài tập lặp lại

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

Cảnh báo của PVS-Studio: V519 [CWE-563] Biến 'NType' được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 1663, 1664. MachOObjectFile.cpp 1664

Tôi không nghĩ có sai lầm thực sự ở đây. Chỉ là một nhiệm vụ lặp đi lặp lại không cần thiết. Nhưng vẫn là một sai lầm.

Tương tự:

  • V519 [CWE-563] Biến “B.NDesc” được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Biến được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 59, 61. coff2yaml.cpp 61

Đoạn N25-N27: Thêm sự phân công lại

Bây giờ chúng ta hãy xem xét một phiên bản tái chỉ định hơi khác một chút.

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

Cảnh báo PVS-Studio: V519 [CWE-563] Biến 'Alignment' được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 1158, 1160. LoadStoreVectorizer.cpp 1160

Đây là mã rất lạ dường như có lỗi logic. Lúc đầu, biến Alignment một giá trị được chỉ định tùy thuộc vào điều kiện. Và sau đó nhiệm vụ lại diễn ra, nhưng bây giờ không có bất kỳ sự kiểm tra nào.

Những tình huống tương tự có thể được nhìn thấy ở đây:

  • V519 [CWE-563] Biến 'Effects' được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] Biến 'ExpectNoDerefChunk' được gán giá trị hai lần liên tiếp. Có lẽ đây là một sai lầm. Kiểm tra các dòng: 4970, 4973. SemaType.cpp 4973

Đoạn N28: Điều kiện luôn đúng

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

Cảnh báo của PVS-Studio: V547 [CWE-571] Biểu thức 'nextByte != 0x90' luôn đúng. X86DisassemblerDecoding.cpp 379

Kiểm tra không có ý nghĩa. Biến đổi nextByte luôn không bằng giá trị 0x90, theo sau lần kiểm tra trước. Đây là một số loại lỗi logic.

Đoạn N29 - N...: Luôn có điều kiện đúng/sai

Máy phân tích đưa ra nhiều cảnh báo rằng toàn bộ tình trạng (V547) Hoặc một phần của nó (V560) luôn đúng hoặc sai. Thường thì đây không phải là lỗi thực sự mà chỉ đơn giản là mã cẩu thả, kết quả của việc mở rộng macro, v.v. Tuy nhiên, việc xem xét tất cả các cảnh báo này là điều hợp lý vì thỉnh thoảng vẫn xảy ra các lỗi logic thực sự. Ví dụ: đoạn mã này đáng ngờ:

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

Cảnh báo của PVS-Studio: V560 [CWE-570] Một phần của biểu thức điều kiện luôn sai: RegNo == 0xe. ARMDisassembler.cpp 939

Hằng số 0xE là giá trị 14 ở dạng thập phân. Bài kiểm tra RegNo == 0xe không có ý nghĩa gì vì nếu RegNo > 13, thì hàm sẽ hoàn thành việc thực thi của nó.

Có nhiều cảnh báo khác với ID V547 và V560, nhưng cũng như với V595, Tôi không quan tâm đến việc nghiên cứu những cảnh báo này. Rõ ràng là tôi đã có đủ tư liệu để viết một bài rồi :). Do đó, vẫn chưa biết có thể xác định được bao nhiêu lỗi loại này trong LLVM bằng cách sử dụng PVS-Studio.

Tôi sẽ cho bạn một ví dụ giải thích tại sao việc nghiên cứu những tác nhân này lại nhàm chán. Trình phân tích hoàn toàn đúng khi đưa ra cảnh báo cho đoạn mã sau. Nhưng đây không phải là một sai lầm.

bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
                                          tok::TokenKind ClosingBraceKind) {
  bool HasError = false;
  ....
  HasError = true;
  if (!ContinueOnSemicolons)
    return !HasError;
  ....
}

Cảnh báo PVS-Studio: V547 [CWE-570] Biểu thức '!HasError' luôn sai. UnwrappedLineParser.cpp 1635

Đoạn N30: ​​Sự trở lại đáng ngờ

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

Cảnh báo của PVS-Studio: V612 [CWE-670] 'Trả lại' vô điều kiện trong một vòng lặp. R600OptimizeVectorRegisters.cpp 63

Đây có thể là một lỗi hoặc một kỹ thuật cụ thể nhằm giải thích điều gì đó cho người lập trình đọc mã. Thiết kế này không giải thích được điều gì với tôi và trông rất đáng ngờ. Tốt nhất đừng viết như vậy :).

Mệt? Sau đó là lúc pha trà hoặc cà phê.

Tìm lỗi trong LLVM 8 bằng máy phân tích PVS-Studio

Khiếm khuyết được xác định bằng chẩn đoán mới

Tôi nghĩ 30 lần kích hoạt chẩn đoán cũ là đủ. Bây giờ chúng ta hãy xem những điều thú vị có thể được tìm thấy với chẩn đoán mới xuất hiện trong máy phân tích sau Trước Séc. Trong thời gian này, tổng cộng 66 chẩn đoán có mục đích chung đã được thêm vào máy phân tích C++.

Đoạn N31: Mã không thể truy cập được

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();
}

Cảnh báo của PVS-Studio: V779 [CWE-561] Đã phát hiện thấy mã không thể truy cập được. Có thể có lỗi. Thi hànhUtils.cpp 146

Như bạn có thể thấy, cả hai nhánh của toán tử if kết thúc bằng một cuộc gọi đến nhà điều hành trở lại. Theo đó, thùng chứa CtorDtorsByPriority sẽ không bao giờ được xóa sạch.

Đoạn N32: Mã không thể truy cập được

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

Cảnh báo PVS-Studio: V779 [CWE-561] Đã phát hiện mã không thể truy cập. Có thể có lỗi. LLParser.cpp 835

Tình huống thú vị. Trước tiên chúng ta hãy nhìn vào nơi này:

return ParseTypeIdEntry(SummaryID);
break;

Thoạt nhìn có vẻ như không có sai sót nào ở đây. Có vẻ như người điều hành phá vỡ có một cái bổ sung ở đây và bạn chỉ cần xóa nó đi. Tuy nhiên, không phải tất cả đều đơn giản như vậy.

Máy phân tích đưa ra cảnh báo trên các dòng:

Lex.setIgnoreColonInIdentifiers(false);
return false;

Và thực sự, mã này không thể truy cập được. Tất cả các trường hợp trong chuyển đổi kết thúc bằng cuộc gọi từ nhà điều hành trở lại. Và giờ đây cô đơn vô nghĩa phá vỡ trông không có vẻ vô hại cho lắm! Có lẽ một trong những nhánh nên kết thúc bằng phá vỡnhưng không phải trên trở lại?

Đoạn N33: Thiết lập lại ngẫu nhiên các bit cao

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

Cảnh báo của PVS-Studio: V784 Kích thước của mặt nạ bit nhỏ hơn kích thước của toán hạng đầu tiên. Điều này sẽ gây ra sự mất mát của các bit cao hơn. RuntimeDyld.cpp 815

Xin lưu ý rằng chức năng getStubAlignment kiểu trả về unsigned. Hãy tính giá trị của biểu thức, giả sử hàm trả về giá trị 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFFF8u

Bây giờ hãy chú ý rằng biến Kích thước dữ liệu có loại không dấu 64-bit. Hóa ra là khi thực hiện thao tác DataSize & 0xFFFFFFF8u, tất cả 0 bit bậc cao sẽ được đặt lại về 8. Rất có thể, đây không phải là điều mà lập trình viên mong muốn. Tôi nghi ngờ rằng anh ấy muốn tính toán: DataSize & XNUMXxFFFFFFFFFFFFFFFFXNUMXu.

Để sửa lỗi, bạn nên viết thế này:

DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);

Hay như vậy:

DataSize &= ~(getStubAlignment() - 1ULL);

Đoạn N34: Truyền kiểu rõ ràng không thành công

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

Cảnh báo của PVS-Studio: V1028 [CWE-190] Có thể tràn. Hãy cân nhắc chuyển các toán hạng của toán tử 'NumElts * Scaling' sang loại 'size_t', chứ không phải kết quả. X86ISelLowering.h 1577

Truyền kiểu rõ ràng được sử dụng để tránh tràn khi nhân các biến kiểu int. Tuy nhiên, việc truyền kiểu rõ ràng ở đây không bảo vệ chống tràn. Đầu tiên, các biến sẽ được nhân và chỉ khi đó kết quả 32 bit của phép nhân mới được mở rộng thành loại kích thước_t.

Đoạn N35: Sao chép-Dán không thành công

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] Hai đoạn mã tương tự đã được tìm thấy. Có lẽ đây là lỗi đánh máy và nên sử dụng biến 'Op1' thay vì 'Op0'. InstCombineCompares.cpp 5507

Chẩn đoán thú vị mới này xác định các tình huống trong đó một đoạn mã đã được sao chép và một số tên trong đó đã bắt đầu bị thay đổi, nhưng ở một chỗ họ chưa sửa nó.

Xin lưu ý rằng trong khối thứ hai họ đã thay đổi Op0 trên Op1. Nhưng có một chỗ họ không sửa được. Rất có thể nó phải được viết như thế này:

if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
  I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
  return &I;
}

Đoạn N36: Sự nhầm lẫn có thể thay đổi

struct Status {
  unsigned Mask;
  unsigned Mode;

  Status() : Mask(0), Mode(0){};

  Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
    Mode &= Mask;
  };
  ....
};

Cảnh báo của PVS-Studio: V1001 [CWE-563] Biến 'Chế độ' được chỉ định nhưng không được sử dụng ở cuối hàm. SIModeRegister.cpp 48

Sẽ rất nguy hiểm nếu đặt cho các đối số hàm cùng tên với các thành viên của lớp. Rất dễ bị nhầm lẫn. Chúng tôi vừa có một trường hợp như vậy trước mắt. Biểu thức này không có ý nghĩa:

Mode &= Mask;

Đối số chức năng thay đổi. Đó là tất cả. Đối số này không còn được sử dụng. Rất có thể bạn nên viết nó như thế này:

Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
  this->Mode &= Mask;
};

Đoạn N37: Sự nhầm lẫn có thể thay đổi

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

Cảnh báo PVS-Studio: V1001 [CWE-563] Biến 'Kích thước' được chỉ định nhưng không được sử dụng ở cuối hàm. Object.cpp 424

Tình hình cũng tương tự như lần trước. Nó nên được viết:

this->Size += this->EntrySize;

Đoạn N38-N47: Họ quên kiểm tra chỉ số

Trước đây, chúng ta đã xem xét các ví dụ về kích hoạt chẩn đoán V595. Bản chất của nó là con trỏ được hủy đăng ký ngay từ đầu và chỉ sau đó mới được kiểm tra. Chẩn đoán trẻ V1004 nghĩa trái ngược nhưng cũng bộc lộ nhiều sai sót. Nó xác định các tình huống trong đó con trỏ được kiểm tra ngay từ đầu và sau đó quên làm như vậy. Hãy xem xét những trường hợp như vậy được tìm thấy bên trong LLVM.

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());  // <=
  ....
}

Cảnh báo PVS-Studio: V1004 [CWE-476] Con trỏ 'Ptr' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra dòng: 729, 738. TargetTransformInfoImpl.h 738

Biến ptr có thể bằng nhau nullptr, được chứng minh bằng việc kiểm tra:

if (Ptr != nullptr)

Tuy nhiên, bên dưới con trỏ này bị hủy đăng ký mà không cần kiểm tra sơ bộ:

auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());

Hãy xem xét một trường hợp tương tự khác.

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(); // <=
  ....
}

Cảnh báo PVS-Studio: V1004 [CWE-476] Con trỏ 'FD' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 3228, 3231. CGDebugInfo.cpp 3231

Hãy chú ý đến dấu hiệu FD. Tôi chắc chắn rằng vấn đề đã được nhìn thấy rõ ràng và không cần phải có lời giải thích đặc biệt nào.

Và xa hơn:

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());     // <=
  ....
}

Cảnh báo PVS-Studio: V1004 [CWE-476] Con trỏ 'PtrTy' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 960, 965. InterleavedLoadCombinePass.cpp 965

Làm thế nào để bảo vệ bản thân khỏi những lỗi như vậy? Hãy chú ý hơn đến Đánh giá mã và sử dụng máy phân tích tĩnh PVS-Studio để thường xuyên kiểm tra mã của bạn.

Không có ích gì khi trích dẫn các đoạn mã khác có lỗi kiểu này. Tôi sẽ chỉ để lại danh sách cảnh báo trong bài viết:

  • V1004 [CWE-476] Con trỏ 'Expr' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] Con trỏ 'PI' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] Con trỏ 'StatepointCall' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] Con trỏ 'RV' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] Con trỏ 'CalleeFn' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra các dòng: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] Con trỏ 'TC' được sử dụng không an toàn sau khi được xác minh dựa trên nullptr. Kiểm tra dòng: 1819, 1824. Driver.cpp 1824

Đoạn N48-N60: Không nghiêm trọng nhưng có lỗi (có thể bị rò rỉ bộ nhớ)

std::unique_ptr<IRMutator> createISelMutator() {
  ....
  std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
  Strategies.emplace_back(
      new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
  ....
}

Cảnh báo của PVS-Studio: V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Chiến lược' bằng phương pháp 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-isel-fuzzer.cpp 58

Để thêm một phần tử vào cuối vùng chứa như std::vectơ > bạn không thể chỉ viết xxx.push_back(X mới), vì không có sự chuyển đổi tiềm ẩn từ X* в std::unique_ptr.

Một giải pháp phổ biến là viết xxx.emplace_back(X mới)kể từ khi nó biên dịch: phương thức chỗ_trở lại xây dựng một phần tử trực tiếp từ các đối số của nó và do đó có thể sử dụng các hàm tạo rõ ràng.

Nó không an toàn. Nếu vectơ đầy thì bộ nhớ sẽ được cấp phát lại. Hoạt động phân bổ lại bộ nhớ có thể không thành công, dẫn đến một ngoại lệ được đưa ra std::bad_alloc. Trong trường hợp này, con trỏ sẽ bị mất và đối tượng đã tạo sẽ không bao giờ bị xóa.

Giải pháp an toàn là tạo duy nhất_ptrsẽ sở hữu con trỏ trước khi vectơ cố gắng phân bổ lại bộ nhớ:

xxx.push_back(std::unique_ptr<X>(new X))

Kể từ C++ 14, bạn có thể sử dụng 'std::make_unique':

xxx.push_back(std::make_unique<X>())

Loại lỗi này không nghiêm trọng đối với LLVM. Nếu không thể cấp phát bộ nhớ, trình biên dịch sẽ dừng lại. Tuy nhiên, đối với các ứng dụng có thời gian dài thời gian hoạt động, không thể chấm dứt nếu việc cấp phát bộ nhớ không thành công, đây có thể là một lỗi thực sự khó chịu.

Vì vậy, mặc dù mã này không gây ra mối đe dọa thực tế cho LLVM, nhưng tôi thấy hữu ích khi nói về mẫu lỗi này và máy phân tích PVS-Studio đã học cách xác định nó.

Các cảnh báo khác thuộc loại này:

  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Passes' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. PassManager.h 546
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'AA' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. Bí danhAnalysis.h 324
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Mục' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'AllEdges' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. CFGMST.h 268
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'VMaps' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Bản ghi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. FDRLogBuilder.h 30
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'PendingSubmodules' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. ModuleMap.cpp 810
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Đối tượng' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. DebugMap.cpp 88
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Chiến lược' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 685
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 686
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 688
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 689
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 690
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 691
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 692
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 693
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Công cụ sửa đổi' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. llvm-stress.cpp 694
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Toán hạng' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Stash' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] Một con trỏ không có chủ sở hữu được thêm vào vùng chứa 'Matchers' bằng phương thức 'emplace_back'. Rò rỉ bộ nhớ sẽ xảy ra trong trường hợp ngoại lệ. GlobalISelEmitter.cpp 2702

Kết luận

Tôi đã đưa ra tổng cộng 60 cảnh báo và sau đó dừng lại. Có khiếm khuyết nào khác mà máy phân tích PVS-Studio phát hiện trong LLVM không? Vâng tôi có. Tuy nhiên, khi tôi đang viết các đoạn mã cho bài báo thì trời đã tối muộn, hay đúng hơn là ban đêm, và tôi quyết định đã đến lúc kết thúc một ngày.

Tôi hy vọng bạn thấy nó thú vị và muốn dùng thử máy phân tích PVS-Studio.

Bạn có thể tải xuống máy phân tích và lấy khóa quét mìn tại trang này.

Quan trọng nhất là sử dụng phân tích tĩnh thường xuyên. Kiểm tra một lần, do chúng tôi thực hiện nhằm phổ biến phương pháp phân tích tĩnh và PVS-Studio không phải là một tình huống bình thường.

Chúc may mắn trong việc cải thiện chất lượng và độ tin cậy của mã của bạn!

Tìm lỗi trong LLVM 8 bằng máy phân tích PVS-Studio

Nếu bạn muốn chia sẻ bài viết này với khán giả nói tiếng Anh, vui lòng sử dụng liên kết dịch: Andrey Karpov. Tìm lỗi trong LLVM 8 với PVS-Studio.

Nguồn: www.habr.com

Thêm một lời nhận xét