Εύρεση σφαλμάτων στο LLVM 8 χρησιμοποιώντας τον αναλυτή PVS-Studio

Εύρεση σφαλμάτων στο LLVM 8 χρησιμοποιώντας τον αναλυτή PVS-Studio
Έχουν περάσει περισσότερα από δύο χρόνια από τον τελευταίο έλεγχο κωδικών του έργου LLVM χρησιμοποιώντας τον αναλυτή PVS-Studio. Ας βεβαιωθούμε ότι ο αναλυτής PVS-Studio εξακολουθεί να είναι ένα κορυφαίο εργαλείο για τον εντοπισμό σφαλμάτων και πιθανών τρωτών σημείων. Για να γίνει αυτό, θα ελέγξουμε και θα βρούμε νέα σφάλματα στην έκδοση LLVM 8.0.0.

Άρθρο που θα γραφτεί

Για να είμαι ειλικρινής, δεν ήθελα να γράψω αυτό το άρθρο. Δεν είναι ενδιαφέρον να γράψουμε για ένα έργο που έχουμε ήδη ελέγξει αρκετές φορές (1, 2, 3). Είναι καλύτερα να γράψω για κάτι νέο, αλλά δεν έχω άλλη επιλογή.

Κάθε φορά που κυκλοφορεί ή ενημερώνεται μια νέα έκδοση του LLVM Clang Static Analyzer, λαμβάνουμε ερωτήσεις του παρακάτω τύπου στην αλληλογραφία μας:

Κοίτα, η νέα έκδοση του Clang Static Analyzer έχει μάθει να βρίσκει νέα σφάλματα! Μου φαίνεται ότι η συνάφεια χρήσης του PVS-Studio μειώνεται. Το Clang βρίσκει περισσότερα λάθη από πριν και ανταποκρίνεται στις δυνατότητες του PVS-Studio. Τι πιστεύετε γι 'αυτό;

Σε αυτό θέλω πάντα να απαντήσω κάτι σαν:

Δεν καθόμαστε και αδρανείς! Έχουμε βελτιώσει σημαντικά τις δυνατότητες του αναλυτή PVS-Studio. Μην ανησυχείτε λοιπόν, συνεχίζουμε να προπορευόμαστε όπως πριν.

Δυστυχώς, αυτή είναι μια κακή απάντηση. Δεν υπάρχουν αποδείξεις σε αυτό. Και γι' αυτό γράφω αυτό το άρθρο τώρα. Έτσι, το έργο LLVM έχει ελεγχθεί για άλλη μια φορά και έχει βρεθεί μια ποικιλία σφαλμάτων σε αυτό. Θα δείξω τώρα αυτά που μου φάνηκαν ενδιαφέροντα. Το Clang Static Analyzer δεν μπορεί να βρει αυτά τα σφάλματα (ή είναι εξαιρετικά άβολο να το κάνετε με τη βοήθειά του). Αλλά μπορούμε. Επιπλέον, βρήκα και έγραψα όλα αυτά τα λάθη σε ένα βράδυ.

Αλλά η συγγραφή του άρθρου χρειάστηκε αρκετές εβδομάδες. Απλώς δεν μπορούσα να τα βάλω όλα αυτά σε κείμενο :).

Παρεμπιπτόντως, εάν ενδιαφέρεστε για το ποιες τεχνολογίες χρησιμοποιούνται στον αναλυτή PVS-Studio για τον εντοπισμό σφαλμάτων και πιθανών τρωτών σημείων, τότε προτείνω να εξοικειωθείτε με αυτό Σημείωση.

Νέα και παλιά διαγνωστικά

Όπως έχει ήδη σημειωθεί, πριν από περίπου δύο χρόνια το έργο LLVM ελέγχθηκε και πάλι και τα σφάλματα που βρέθηκαν διορθώθηκαν. Τώρα αυτό το άρθρο θα παρουσιάσει μια νέα παρτίδα σφαλμάτων. Γιατί βρέθηκαν νέα σφάλματα; Υπάρχουν 3 λόγοι για αυτό:

  1. Το έργο LLVM εξελίσσεται, αλλάζει τον παλιό κώδικα και προσθέτει νέο κώδικα. Φυσικά, υπάρχουν νέα λάθη στον τροποποιημένο και γραπτό κώδικα. Αυτό δείχνει ξεκάθαρα ότι η στατική ανάλυση πρέπει να χρησιμοποιείται τακτικά και όχι περιστασιακά. Τα άρθρα μας δείχνουν καλά τις δυνατότητες του αναλυτή PVS-Studio, αλλά αυτό δεν έχει καμία σχέση με τη βελτίωση της ποιότητας του κώδικα και τη μείωση του κόστους διόρθωσης σφαλμάτων. Χρησιμοποιείτε τακτικά έναν αναλυτή στατικού κώδικα!
  2. Ολοκληρώνουμε και βελτιώνουμε τα υπάρχοντα διαγνωστικά. Επομένως, ο αναλυτής μπορεί να εντοπίσει σφάλματα που δεν παρατήρησε κατά τις προηγούμενες σαρώσεις.
  3. Στο PVS-Studio εμφανίστηκαν νέα διαγνωστικά που δεν υπήρχαν πριν από 2 χρόνια. Αποφάσισα να τα επισημάνω σε ξεχωριστή ενότητα για να δείξω ξεκάθαρα την εξέλιξη του PVS-Studio.

Βλάβες που εντοπίστηκαν από διαγνωστικά που υπήρχαν πριν από 2 χρόνια

Fragment N1: Copy-Paste

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: V501 [CWE-570] Υπάρχουν πανομοιότυπες υπο-εκφράσεις "Name.startswith("avx512.mask.permvar.")" στα αριστερά και στα δεξιά του "||" χειριστής. AutoUpgrade.cpp 73

Ελέγχεται διπλά ότι το όνομα αρχίζει με τη δευτερεύουσα συμβολοσειρά "avx512.mask.permvar.". Στον δεύτερο έλεγχο, προφανώς ήθελαν να γράψουν κάτι άλλο, αλλά ξέχασαν να διορθώσουν το αντιγραμμένο κείμενο.

Θραύσμα Ν2: Τυπικό λάθος

enum CXNameRefFlags {
  CXNameRange_WantQualifier = 0x1,
  CXNameRange_WantTemplateArgs = 0x2,
  CXNameRange_WantSinglePiece = 0x4
};

void AnnotateTokensWorker::HandlePostPonedChildCursor(
    CXCursor Cursor, unsigned StartTokenIndex) {
  const auto flags = CXNameRange_WantQualifier | CXNameRange_WantQualifier;
  ....
}

Προειδοποίηση PVS-Studio: V501 Υπάρχουν πανομοιότυπες υπο-εκφράσεις 'CXNameRange_WantQualifier' στα αριστερά και στα δεξιά του '|' χειριστής. CIndex.cpp 7245

Λόγω τυπογραφικού λάθους, η ίδια σταθερά χρησιμοποιείται δύο φορές CXNameRange_WantQualifier.

Fragment N3: Σύγχυση με την προτεραιότητα του τελεστή

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

Προειδοποίηση PVS-Studio: V502 [CWE-783] Ίσως ο τελεστής '?:' να λειτουργεί με διαφορετικό τρόπο από ό,τι αναμενόταν. Ο τελεστής '?:' έχει χαμηλότερη προτεραιότητα από τον τελεστή '=='. PPCTargetTransformInfo.cpp 404

Κατά τη γνώμη μου, αυτό είναι ένα πολύ όμορφο λάθος. Ναι, ξέρω ότι έχω περίεργες ιδέες για την ομορφιά :).

Τώρα, σύμφωνα με προτεραιότητες του χειριστή, η έκφραση αξιολογείται ως εξής:

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

Από πρακτική άποψη, μια τέτοια συνθήκη δεν έχει νόημα, καθώς μπορεί να περιοριστεί σε:

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

Αυτό είναι ξεκάθαρο λάθος. Πιθανότατα, ήθελαν να συγκρίνουν το 0/1 με μια μεταβλητή Περιεχόμενα. Για να διορθώσετε τον κώδικα, πρέπει να προσθέσετε παρενθέσεις γύρω από τον τριαδικό τελεστή:

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

Παρεμπιπτόντως, ο τριαδικός χειριστής είναι πολύ επικίνδυνος και προκαλεί λογικά λάθη. Να είστε πολύ προσεκτικοί με αυτό και να μην είστε άπληστοι με παρενθέσεις. Κοίταξα αυτό το θέμα πιο αναλυτικά εδώ, στο κεφάλαιο «Προσοχή στο ?: Χειριστής και περικλείστε το σε παρένθεση».

Fragment N4, N5: Μηδενικός δείκτης

Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
  ....
  TypedInit *LHS = dyn_cast<TypedInit>(Result);
  ....
  LHS = dyn_cast<TypedInit>(
    UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get())
      ->Fold(CurRec));
  if (!LHS) {
    Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() +
                    "' to string");
    return nullptr;
  }
  ....
}

Προειδοποίηση PVS-Studio: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «LHS». TGParser.cpp 2152

Αν ο δείκτης LHS είναι μηδενικό, θα πρέπει να εκδοθεί προειδοποίηση. Ωστόσο, αντί αυτού, ο ίδιος μηδενικός δείκτης θα αποσυναρμολογηθεί: LHS->getAsString().

Αυτή είναι μια πολύ τυπική κατάσταση όταν ένα σφάλμα είναι κρυμμένο σε ένα πρόγραμμα χειρισμού σφαλμάτων, αφού κανείς δεν το δοκιμάζει. Οι στατικοί αναλυτές ελέγχουν όλους τους προσβάσιμους κωδικούς, ανεξάρτητα από το πόσο συχνά χρησιμοποιείται. Αυτό είναι ένα πολύ καλό παράδειγμα για το πώς η στατική ανάλυση συμπληρώνει άλλες τεχνικές δοκιμών και προστασίας από σφάλματα.

Παρόμοιο σφάλμα χειρισμού δείκτη RHS επιτρέπεται στον κωδικό ακριβώς παρακάτω: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «RHS». TGParser.cpp 2186

Θραύσμα N6: Χρήση του δείκτη μετά τη μετακίνηση

static Expected<bool>
ExtractBlocks(....)
{
  ....
  std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap);
  ....
  BD.setNewProgram(std::move(ProgClone));                                // <=
  MiscompiledFunctions.clear();

  for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
    Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);  // <=
    assert(NewF && "Function not found??");
    MiscompiledFunctions.push_back(NewF);
  }
  ....
}

PVS-Studio Προειδοποίηση: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «ProgClone». Λανθασμένη μεταγλώττιση.cpp 601

Στην αρχή ένας έξυπνος δείκτης ProgClone παύει να κατέχει το αντικείμενο:

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

Στην πραγματικότητα, τώρα ProgClone είναι μηδενικός δείκτης. Επομένως, μια παραπομπή μηδενικού δείκτη θα πρέπει να εμφανίζεται ακριβώς παρακάτω:

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

Αλλά, στην πραγματικότητα, αυτό δεν θα συμβεί! Σημειώστε ότι ο βρόχος δεν εκτελείται στην πραγματικότητα.

Στην αρχή του δοχείου Λανθασμένα μεταγλωττισμένες συναρτήσεις εκκαθαρίστηκε:

MiscompiledFunctions.clear();

Στη συνέχεια, το μέγεθος αυτού του δοχείου χρησιμοποιείται στην κατάσταση βρόχου:

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

Είναι εύκολο να δεις ότι ο βρόχος δεν ξεκινά. Νομίζω ότι αυτό είναι επίσης ένα σφάλμα και ο κώδικας πρέπει να γραφτεί διαφορετικά.

Φαίνεται ότι έχουμε συναντήσει εκείνη την περίφημη ισοτιμία λαθών! Το ένα λάθος καλύπτει το άλλο :).

Θραύσμα N7: Χρήση του δείκτη μετά τη μετακίνηση

static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test,
                                    std::unique_ptr<Module> Safe) {
  outs() << "  Optimizing functions being tested: ";
  std::unique_ptr<Module> Optimized =
      BD.runPassesOn(Test.get(), BD.getPassesToRun());
  if (!Optimized) {
    errs() << " Error running this sequence of passes"
           << " on the input program!n";
    BD.setNewProgram(std::move(Test));                       // <=
    BD.EmitProgressBitcode(*Test, "pass-error", false);      // <=
    if (Error E = BD.debugOptimizerCrash())
      return std::move(E);
    return false;
  }
  ....
}

Προειδοποίηση PVS-Studio: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «Test». Λανθασμένη μεταγλώττιση.cpp 709

Πάλι η ίδια κατάσταση. Αρχικά, τα περιεχόμενα του αντικειμένου μετακινούνται και στη συνέχεια χρησιμοποιείται σαν να μην είχε συμβεί τίποτα. Βλέπω αυτή την κατάσταση όλο και πιο συχνά στον κώδικα προγράμματος μετά την εμφάνιση της σημασιολογίας κίνησης στη C++. Αυτός είναι ο λόγος που λατρεύω τη γλώσσα C++! Υπάρχουν ολοένα και περισσότεροι νέοι τρόποι για να τραβήξετε το δικό σας πόδι. Ο αναλυτής PVS-Studio θα έχει πάντα δουλειά :).

Fragment N8: Μηδενικός δείκτης

void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
  uint32_t TypeId = Symbol.getTypeId();
  auto Type = Symbol.getSession().getSymbolById(TypeId);
  if (Type)
    Printer << "<unknown-type>";
  else
    Type->dump(*this);
}

Προειδοποίηση PVS-Studio: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «Τύπος». PrettyFunctionDumper.cpp 233

Εκτός από τους χειριστές σφαλμάτων, συνήθως δεν ελέγχονται οι λειτουργίες εκτύπωσης εντοπισμού σφαλμάτων. Έχουμε ακριβώς μια τέτοια περίπτωση μπροστά μας. Η λειτουργία περιμένει τον χρήστη, ο οποίος, αντί να λύσει τα προβλήματά του, θα αναγκαστεί να τη διορθώσει.

Σωστά:

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

Fragment N9: Μηδενικός δείκτης

void SearchableTableEmitter::collectTableEntries(
    GenericTable &Table, const std::vector<Record *> &Items) {
  ....
  RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
  if (!Ty)                                                              // <=
    PrintFatalError(Twine("Field '") + Field.Name + "' of table '" +
                    Table.Name + "' has incompatible type: " +
                    Ty->getAsString() + " vs. " +                       // <=
                    TI->getType()->getAsString());
   ....
}

Προειδοποίηση PVS-Studio: V522 [CWE-476] Ενδέχεται να πραγματοποιηθεί αποαναφορά του μηδενικού δείκτη «Ty». SearchableTableEmitter.cpp 614

Νομίζω ότι όλα είναι ξεκάθαρα και δεν χρειάζονται εξηγήσεις.

Θραύσμα Ν10: Τυπικό λάθος

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: V570 Η μεταβλητή 'Identifier->Type' εκχωρείται στον εαυτό της. FormatTokenLexer.cpp 249

Δεν έχει νόημα να εκχωρεί μια μεταβλητή στον εαυτό της. Πιθανότατα ήθελαν να γράψουν:

Identifier->Type = Question->Type;

Θραύσμα N11: Ύποπτο σπάσιμο

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

Προειδοποίηση PVS-Studio: V622 [CWE-478] Εξετάστε το ενδεχόμενο να επιθεωρήσετε τη δήλωση «διακόπτης». Είναι πιθανό να λείπει ο πρώτος χειριστής 'case'. SystemZAsmParser.cpp 652

Υπάρχει ένας πολύ ύποπτος χειριστής στην αρχή σπάσει. Ξέχασες να γράψεις κάτι άλλο εδώ;

Θραύσμα N12: Έλεγχος ενός δείκτη μετά την κατάργηση αναφοράς

InlineCost AMDGPUInliner::getInlineCost(CallSite CS) {
  Function *Callee = CS.getCalledFunction();
  Function *Caller = CS.getCaller();
  TargetTransformInfo &TTI = TTIWP->getTTI(*Callee);

  if (!Callee || Callee->isDeclaration())
    return llvm::InlineCost::getNever("undefined callee");
  ....
}

Προειδοποίηση PVS-Studio: V595 [CWE-476] Ο δείκτης «Callee» χρησιμοποιήθηκε πριν επαληθευτεί έναντι του nullptr. Έλεγχος γραμμών: 172, 174. AMDGPUInline.cpp 172

Δείκτης Callee στην αρχή αποαναφέρεται τη στιγμή που καλείται η συνάρτηση getTTI.

Και τότε αποδεικνύεται ότι αυτός ο δείκτης πρέπει να ελεγχθεί για ισότητα nullptr:

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

Αλλά είναι πολύ αργά…

Θραύσμα N13 - N...: Έλεγχος δείκτη μετά από αποσύνδεση

Η κατάσταση που συζητήθηκε στο προηγούμενο τμήμα κώδικα δεν είναι μοναδική. Εμφανίζεται εδώ:

static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B,
                               bool isBinary, bool isPrecise = false) {
  ....
  Function *CalleeFn = CI->getCalledFunction();
  StringRef CalleeNm = CalleeFn->getName();                 // <=
  AttributeList CalleeAt = CalleeFn->getAttributes();
  if (CalleeFn && !CalleeFn->isIntrinsic()) {               // <=
  ....
}

Προειδοποίηση PVS-Studio: V595 [CWE-476] Ο δείκτης 'CalleeFn' χρησιμοποιήθηκε πριν επαληθευτεί έναντι του nullptr. Ελέγξτε τις γραμμές: 1079, 1081. SimplifyLibCalls.cpp 1079

Και εδώ:

void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
                            const Decl *Tmpl, Decl *New,
                            LateInstantiatedAttrVec *LateAttrs,
                            LocalInstantiationScope *OuterMostScope) {
  ....
  NamedDecl *ND = dyn_cast<NamedDecl>(New);
  CXXRecordDecl *ThisContext =
    dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());         // <=
  CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
                             ND && ND->isCXXInstanceMember());     // <=
  ....
}

Προειδοποίηση PVS-Studio: V595 [CWE-476] Ο δείκτης 'ND' χρησιμοποιήθηκε πριν επαληθευτεί έναντι του nullptr. Ελέγξτε τις γραμμές: 532, 534. SemaTemplateInstantiateDecl.cpp 532

Και εδώ:

  • V595 [CWE-476] Ο δείκτης 'U' χρησιμοποιήθηκε πριν επαληθευτεί έναντι του nullptr. Έλεγχος γραμμών: 404, 407. DWARFormValue.cpp 404
  • V595 [CWE-476] Ο δείκτης 'ND' χρησιμοποιήθηκε πριν επαληθευτεί έναντι του nullptr. Ελέγξτε τις γραμμές: 2149, 2151. SemaTemplateInstantiate.cpp 2149

Και μετά δεν με ενδιέφερε να μελετήσω τις προειδοποιήσεις με τον αριθμό V595. Επομένως, δεν ξέρω αν υπάρχουν περισσότερα παρόμοια σφάλματα εκτός από αυτά που αναφέρονται εδώ. Το πιθανότερο είναι να υπάρχει.

Θραύσμα N17, N18: Ύποπτη μετατόπιση

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

Προειδοποίηση PVS-Studio: V629 [CWE-190] Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση '~(Μέγεθος - 1) << 1'. Μετατόπιση bit της τιμής των 32 bit με επακόλουθη επέκταση στον τύπο 64 bit. AArch64AddressingModes.h 260

Μπορεί να μην είναι σφάλμα και ο κώδικας λειτουργεί ακριβώς όπως προβλέπεται. Αλλά αυτό είναι σαφώς ένα πολύ ύποπτο μέρος και πρέπει να ελεγχθεί.

Ας πούμε τη μεταβλητή Μέγεθος ισούται με 16 και στη συνέχεια ο συγγραφέας του κώδικα σχεδίαζε να τον πάρει σε μια μεταβλητή NImms αξία:

1111111111111111111111111111111111111111111111111111111111100000

Ωστόσο, στην πραγματικότητα το αποτέλεσμα θα είναι:

0000000000000000000000000000000011111111111111111111111111100000

Το γεγονός είναι ότι όλοι οι υπολογισμοί πραγματοποιούνται με χρήση του τύπου 32-bit χωρίς υπογραφή. Και μόνο τότε, αυτός ο ανυπόγραφος τύπος 32-bit θα επεκταθεί σιωπηρά σε uint64_t. Σε αυτήν την περίπτωση, τα πιο σημαντικά bits θα είναι μηδέν.

Μπορείτε να διορθώσετε την κατάσταση ως εξής:

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

Παρόμοια κατάσταση: V629 [CWE-190] Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση 'Immr << 6'. Μετατόπιση bit της τιμής των 32 bit με επακόλουθη επέκταση στον τύπο 64 bit. AArch64AddressingModes.h 269

Τμήμα N19: Λείπει λέξη-κλειδί αλλιώς?

void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
  ....
  if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
    // VOP2b (v_add_u32, v_sub_u32 ...) dpp use "vcc" token.
    // Skip it.
    continue;
  } if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {    // <=
    Op.addRegWithFPInputModsOperands(Inst, 2);
  } else if (Op.isDPPCtrl()) {
    Op.addImmOperands(Inst, 1);
  } else if (Op.isImm()) {
    // Handle optional arguments
    OptionalIdx[Op.getImmTy()] = I;
  } else {
    llvm_unreachable("Invalid operand type");
  }
  ....
}

Προειδοποίηση PVS-Studio: V646 [CWE-670] Εξετάστε το ενδεχόμενο να επιθεωρήσετε τη λογική της εφαρμογής. Είναι πιθανό να λείπει η λέξη-κλειδί "άλλο". AMDGPUAsmParser.cpp 5655

Δεν υπάρχει λάθος εδώ. Από το τότε μπλοκ του πρώτου if τελειώνει με ΣΥΝΕΧΕΙΑ, τότε δεν πειράζει, υπάρχει λέξη-κλειδί αλλιώς ή όχι. Σε κάθε περίπτωση, ο κώδικας θα λειτουργεί το ίδιο. Ακόμα χαμένο αλλιώς κάνει τον κώδικα πιο ασαφή και επικίνδυνο. Αν στο μέλλον ΣΥΝΕΧΕΙΑ εξαφανίζεται, ο κωδικός θα αρχίσει να λειτουργεί εντελώς διαφορετικά. Κατά τη γνώμη μου είναι καλύτερο να προσθέσω αλλιώς.

Fragment N20: Τέσσερα τυπογραφικά λάθη του ίδιου τύπου

LLVM_DUMP_METHOD void Symbol::dump(raw_ostream &OS) const {
  std::string Result;
  if (isUndefined())
    Result += "(undef) ";
  if (isWeakDefined())
    Result += "(weak-def) ";
  if (isWeakReferenced())
    Result += "(weak-ref) ";
  if (isThreadLocalValue())
    Result += "(tlv) ";
  switch (Kind) {
  case SymbolKind::GlobalSymbol:
    Result + Name.str();                        // <=
    break;
  case SymbolKind::ObjectiveCClass:
    Result + "(ObjC Class) " + Name.str();      // <=
    break;
  case SymbolKind::ObjectiveCClassEHType:
    Result + "(ObjC Class EH) " + Name.str();   // <=
    break;
  case SymbolKind::ObjectiveCInstanceVariable:
    Result + "(ObjC IVar) " + Name.str();       // <=
    break;
  }
  OS << Result;
}

Προειδοποιήσεις PVS-Studio:

  • V655 [CWE-480] Οι χορδές συνενώθηκαν αλλά δεν χρησιμοποιούνται. Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση "Αποτέλεσμα + Όνομα.str()". Symbol.cpp 32
  • V655 [CWE-480] Οι χορδές συνενώθηκαν αλλά δεν χρησιμοποιούνται. Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση "Αποτέλεσμα + "(Κλάση ObjC)" + Όνομα.str()". Symbol.cpp 35
  • V655 [CWE-480] Οι χορδές συνενώθηκαν αλλά δεν χρησιμοποιούνται. Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση "Αποτέλεσμα + "(ObjC Class EH) " + Name.str()". Symbol.cpp 38
  • V655 [CWE-480] Οι χορδές συνενώθηκαν αλλά δεν χρησιμοποιούνται. Εξετάστε το ενδεχόμενο να επιθεωρήσετε την έκφραση "Αποτέλεσμα + "(ObjC IVar)" + Name.str()". Symbol.cpp 41

Κατά λάθος, χρησιμοποιείται ο τελεστής + αντί του τελεστή +=. Το αποτέλεσμα είναι σχέδια που στερούνται νοήματος.

Fragment N21: Απροσδιόριστη συμπεριφορά

static void getReqFeatures(std::map<StringRef, int> &FeaturesMap,
                           const std::vector<Record *> &ReqFeatures) {
  for (auto &R : ReqFeatures) {
    StringRef AsmCondString = R->getValueAsString("AssemblerCondString");

    SmallVector<StringRef, 4> Ops;
    SplitString(AsmCondString, Ops, ",");
    assert(!Ops.empty() && "AssemblerCondString cannot be empty");

    for (auto &Op : Ops) {
      assert(!Op.empty() && "Empty operator");
      if (FeaturesMap.find(Op) == FeaturesMap.end())
        FeaturesMap[Op] = FeaturesMap.size();
    }
  }
}

Προσπαθήστε να βρείτε μόνοι σας τον επικίνδυνο κωδικό. Και αυτή είναι μια εικόνα που αποσπά την προσοχή για να μην κοιτάξει αμέσως την απάντηση:

Εύρεση σφαλμάτων στο LLVM 8 χρησιμοποιώντας τον αναλυτή PVS-Studio

Προειδοποίηση PVS-Studio: V708 [CWE-758] Χρησιμοποιείται επικίνδυνη κατασκευή: 'FeaturesMap[Op] = FeaturesMap.size()', όπου το 'FeaturesMap' είναι κατηγορίας 'map'. Αυτό μπορεί να οδηγήσει σε απροσδιόριστη συμπεριφορά. RISCVCompressInstEmitter.cpp 490

Γραμμή προβλήματος:

FeaturesMap[Op] = FeaturesMap.size();

Αν στοιχείο Op δεν βρίσκεται, τότε δημιουργείται ένα νέο στοιχείο στον χάρτη και ο αριθμός των στοιχείων σε αυτόν τον χάρτη γράφεται εκεί. Είναι απλώς άγνωστο αν θα κληθεί η συνάρτηση μέγεθος πριν ή μετά την προσθήκη ενός νέου στοιχείου.

Fragment N22-N24: Επαναλαμβανόμενες εργασίες

Error MachOObjectFile::checkSymbolTable() const {
  ....
  } else {
    MachO::nlist STE = getSymbolTableEntry(SymDRI);
    NType = STE.n_type;                              // <=
    NType = STE.n_type;                              // <=
    NSect = STE.n_sect;
    NDesc = STE.n_desc;
    NStrx = STE.n_strx;
    NValue = STE.n_value;
  }
  ....
}

Προειδοποίηση PVS-Studio: V519 [CWE-563] Στη μεταβλητή 'NType' εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Ελέγξτε τις γραμμές: 1663, 1664. MachOObjectFile.cpp 1664

Δεν νομίζω ότι υπάρχει πραγματικό λάθος εδώ. Απλά μια περιττή επαναλαμβανόμενη εργασία. Αλλά και πάλι μια γκάφα.

Ομοίως:

  • V519 [CWE-563] Στη μεταβλητή 'B.NDesc' εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Έλεγχος γραμμών: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] Στη μεταβλητή εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Γραμμές ελέγχου: 59, 61. coff2yaml.cpp 61

Θραύσμα N25-N27: Περισσότερες επανατοποθετήσεις

Τώρα ας δούμε μια ελαφρώς διαφορετική εκδοχή της εκ νέου ανάθεσης.

bool Vectorizer::vectorizeLoadChain(
    ArrayRef<Instruction *> Chain,
    SmallPtrSet<Instruction *, 16> *InstructionsProcessed) {
  ....
  unsigned Alignment = getAlignment(L0);
  ....
  unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(),
                                                 StackAdjustedAlignment,
                                                 DL, L0, nullptr, &DT);
  if (NewAlign != 0)
    Alignment = NewAlign;
  Alignment = NewAlign;
  ....
}

Προειδοποίηση PVS-Studio: V519 [CWE-563] Στη μεταβλητή 'Alignment' εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Έλεγχος γραμμών: 1158, 1160. LoadStoreVetorizer.cpp 1160

Αυτός είναι πολύ περίεργος κώδικας που προφανώς περιέχει ένα λογικό σφάλμα. Στην αρχή, μεταβλητή ευθυγραμμία εκχωρείται μια τιμή ανάλογα με την κατάσταση. Και τότε η ανάθεση γίνεται ξανά, αλλά τώρα χωρίς κανένα έλεγχο.

Παρόμοιες καταστάσεις μπορείτε να δείτε εδώ:

  • V519 [CWE-563] Στη μεταβλητή «Εφέ» εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Ελέγξτε τις γραμμές: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] Στη μεταβλητή 'ExpectNoDerefChunk' εκχωρούνται τιμές δύο φορές διαδοχικά. Ίσως αυτό είναι λάθος. Έλεγχος γραμμών: 4970, 4973. SemaType.cpp 4973

Fragment N28: Πάντα αληθινή κατάσταση

static int readPrefixes(struct InternalInstruction* insn) {
  ....
  uint8_t byte = 0;
  uint8_t nextByte;
  ....
  if (byte == 0xf3 && (nextByte == 0x88 || nextByte == 0x89 ||
                       nextByte == 0xc6 || nextByte == 0xc7)) {
    insn->xAcquireRelease = true;
    if (nextByte != 0x90) // PAUSE instruction support             // <=
      break;
  }
  ....
}

Προειδοποίηση PVS-Studio: V547 [CWE-571] Η έκφραση 'nextByte != 0x90' είναι πάντα αληθής. X86DisassemblerDecoder.cpp 379

Ο έλεγχος δεν έχει νόημα. Μεταβλητός nextByte πάντα όχι ίση με την αξία 0x90, που προκύπτει από τον προηγούμενο έλεγχο. Αυτό είναι ένα είδος λογικού σφάλματος.

Fragment N29 - N...: Πάντα αληθές/λάθος συνθήκες

Ο αναλυτής εκδίδει πολλές προειδοποιήσεις ότι ολόκληρη η κατάσταση (V547) ή μέρος αυτού (V560) είναι πάντα αληθές ή ψευδές. Συχνά αυτά δεν είναι πραγματικά σφάλματα, αλλά απλώς ατημέλητος κώδικας, αποτέλεσμα επέκτασης μακροεντολών και άλλα παρόμοια. Ωστόσο, είναι λογικό να εξετάζουμε όλες αυτές τις προειδοποιήσεις, καθώς κατά καιρούς συμβαίνουν γνήσια λογικά σφάλματα. Για παράδειγμα, αυτή η ενότητα κώδικα είναι ύποπτη:

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: V560 [CWE-570] Ένα μέρος της έκφρασης υπό όρους είναι πάντα ψευδές: RegNo == 0xe. ARMdisassembler.cpp 939

Η σταθερά 0xE είναι η τιμή 14 σε δεκαδικό. Εξέταση RegNo == 0xe δεν έχει νόημα γιατί αν RegNo > 13, τότε η συνάρτηση θα ολοκληρώσει την εκτέλεσή της.

Υπήρχαν πολλές άλλες προειδοποιήσεις με τα αναγνωριστικά V547 και V560, αλλά όπως και με V595, δεν με ενδιέφερε να μελετήσω αυτές τις προειδοποιήσεις. Ήταν ήδη ξεκάθαρο ότι είχα αρκετό υλικό για να γράψω ένα άρθρο :). Επομένως, είναι άγνωστο πόσα σφάλματα αυτού του τύπου μπορούν να εντοπιστούν στο LLVM χρησιμοποιώντας το PVS-Studio.

Θα σας δώσω ένα παράδειγμα γιατί η μελέτη αυτών των ερεθισμάτων είναι βαρετή. Ο αναλυτής έχει απόλυτο δίκιο όταν εκδίδει μια προειδοποίηση για τον ακόλουθο κωδικό. Αυτό όμως δεν είναι λάθος.

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

PVS-Studio Προειδοποίηση: V547 [CWE-570] Η έκφραση '!HasError' είναι πάντα ψευδής. UnwrappedLineParser.cpp 1635

Fragment N30: ​​Ύποπτη επιστροφή

static bool
isImplicitlyDef(MachineRegisterInfo &MRI, unsigned Reg) {
  for (MachineRegisterInfo::def_instr_iterator It = MRI.def_instr_begin(Reg),
      E = MRI.def_instr_end(); It != E; ++It) {
    return (*It).isImplicitDef();
  }
  ....
}

Προειδοποίηση PVS-Studio: V612 [CWE-670] Μια άνευ όρων «επιστροφή» μέσα σε έναν βρόχο. R600OptimizeVectorRegisters.cpp 63

Αυτό είναι είτε σφάλμα είτε μια συγκεκριμένη τεχνική που έχει σκοπό να εξηγήσει κάτι στους προγραμματιστές που διαβάζουν τον κώδικα. Αυτό το σχέδιο δεν μου εξηγεί τίποτα και φαίνεται πολύ ύποπτο. Καλύτερα να μην γράφεις έτσι :).

Κουρασμένος? Τότε είναι η ώρα να φτιάξετε τσάι ή καφέ.

Εύρεση σφαλμάτων στο LLVM 8 χρησιμοποιώντας τον αναλυτή PVS-Studio

Βλάβες που εντοπίστηκαν από νέα διαγνωστικά

Νομίζω ότι 30 ενεργοποιήσεις παλαιών διαγνωστικών είναι αρκετές. Ας δούμε τώρα τι ενδιαφέροντα πράγματα μπορούν να βρεθούν με τα νέα διαγνωστικά που εμφανίστηκαν στον αναλυτή μετά προηγούμενος επιταγές. Συνολικά, 66 διαγνωστικά γενικής χρήσης προστέθηκαν στον αναλυτή C++ κατά τη διάρκεια αυτής της περιόδου.

Fragment N31: Κωδικός που δεν είναι προσβάσιμος

Error CtorDtorRunner::run() {
  ....
  if (auto CtorDtorMap =
          ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names),
                    NoDependenciesToRegister, true))
  {
    ....
    return Error::success();
  } else
    return CtorDtorMap.takeError();

  CtorDtorsByPriority.clear();

  return Error::success();
}

Προειδοποίηση PVS-Studio: V779 [CWE-561] Εντοπίστηκε μη προσβάσιμος κωδικός. Είναι πιθανό να υπάρχει κάποιο σφάλμα. ExecutionUtils.cpp 146

Όπως μπορείτε να δείτε, και οι δύο κλάδοι του χειριστή if τελειώνει με μια κλήση στον χειριστή απόδοση. Αντίστοιχα, το δοχείο CtorDtorsByPriority δεν θα εκκαθαριστεί ποτέ.

Fragment N32: Κωδικός που δεν είναι προσβάσιμος

bool LLParser::ParseSummaryEntry() {
  ....
  switch (Lex.getKind()) {
  case lltok::kw_gv:
    return ParseGVEntry(SummaryID);
  case lltok::kw_module:
    return ParseModuleEntry(SummaryID);
  case lltok::kw_typeid:
    return ParseTypeIdEntry(SummaryID);                        // <=
    break;                                                     // <=
  default:
    return Error(Lex.getLoc(), "unexpected summary kind");
  }
  Lex.setIgnoreColonInIdentifiers(false);                      // <=
  return false;
}

Προειδοποίηση PVS-Studio: V779 [CWE-561] Εντοπίστηκε μη προσβάσιμος κωδικός. Είναι πιθανό να υπάρχει κάποιο σφάλμα. LLParser.cpp 835

Ενδιαφέρουσα κατάσταση. Ας δούμε πρώτα αυτό το μέρος:

return ParseTypeIdEntry(SummaryID);
break;

Με την πρώτη ματιά, φαίνεται ότι δεν υπάρχει λάθος εδώ. Μοιάζει με τον χειριστή σπάσει υπάρχει ένα επιπλέον εδώ, και μπορείτε απλά να το διαγράψετε. Ωστόσο, δεν είναι όλα τόσο απλά.

Ο αναλυτής εκδίδει μια προειδοποίηση στις γραμμές:

Lex.setIgnoreColonInIdentifiers(false);
return false;

Και πράγματι, αυτός ο κωδικός είναι απρόσιτος. Όλες οι περιπτώσεις σε αλλαγή τελειώνει με μια κλήση από τον χειριστή απόδοση. Και τώρα παράλογο μόνος σπάσει δεν φαίνεται τόσο ακίνδυνο! Ίσως ένας από τους κλάδους θα πρέπει να τελειώσει με σπάσειόχι απόδοση?

Fragment N33: Τυχαία επαναφορά υψηλών bits

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: V784 Το μέγεθος της μάσκας bit είναι μικρότερο από το μέγεθος του πρώτου τελεστή. Αυτό θα προκαλέσει την απώλεια υψηλότερων bits. RuntimeDyld.cpp 815

Σημειώστε ότι η λειτουργία getStubAlignment επιστρέφει τύπο ανυπόγραφο. Ας υπολογίσουμε την τιμή της παράστασης, υποθέτοντας ότι η συνάρτηση επιστρέφει την τιμή 8:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFFF8u

Τώρα παρατηρήστε ότι η μεταβλητή Μέγεθος Δεδομένων έχει ανυπόγραφο τύπο 64 bit. Αποδεικνύεται ότι κατά την εκτέλεση της λειτουργίας DataSize & 0xFFFFFFF8u, και τα τριάντα δύο bit υψηλής τάξης θα μηδενιστούν. Πιθανότατα, αυτό δεν ήταν αυτό που ήθελε ο προγραμματιστής. Υποψιάζομαι ότι ήθελε να υπολογίσει: DataSize & 0xFFFFFFFFFFFFFFFFFF8u.

Για να διορθώσετε το σφάλμα, θα πρέπει να γράψετε αυτό:

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

Ή έτσι:

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

Fragment N34: Αποτυχία εκπομπών ρητού τύπου

template <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
                      SmallVectorImpl<T> &ScaledMask) {
  assert(0 < Scale && "Unexpected scaling factor");
  int NumElts = Mask.size();
  ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
  ....
}

Προειδοποίηση PVS-Studio: V1028 [CWE-190] Πιθανή υπερχείλιση. Εξετάστε το ενδεχόμενο να μεταφέρετε τελεστές του τελεστή 'NumElts * Scale' στον τύπο 'size_t' και όχι στο αποτέλεσμα. X86ISelLowering.h 1577

Η χύτευση ρητού τύπου χρησιμοποιείται για την αποφυγή υπερχείλισης κατά τον πολλαπλασιασμό των μεταβλητών τύπου int. Ωστόσο, η χύτευση σαφούς τύπου εδώ δεν προστατεύει από υπερχείλιση. Αρχικά, οι μεταβλητές θα πολλαπλασιαστούν και μόνο τότε το αποτέλεσμα των 32 bit του πολλαπλασιασμού θα επεκταθεί στον τύπο μέγεθος_t.

Fragment N35: Failed Copy-Paste

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] Βρέθηκαν δύο παρόμοια τμήματα κώδικα. Ίσως, αυτό είναι τυπογραφικό λάθος και η μεταβλητή 'Op1' θα πρέπει να χρησιμοποιείται αντί για 'Op0'. InstCombineCompares.cpp 5507

Αυτό το νέο ενδιαφέρον διαγνωστικό προσδιορίζει καταστάσεις όπου ένα κομμάτι κώδικα έχει αντιγραφεί και ορισμένα ονόματα σε αυτό έχουν αρχίσει να αλλάζουν, αλλά σε ένα σημείο δεν το έχουν διορθώσει.

Σημειώστε ότι στο δεύτερο μπλοκ άλλαξαν Op0 επί Op1. Αλλά σε ένα μέρος δεν το διόρθωσαν. Πιθανότατα θα έπρεπε να είχε γραφτεί έτσι:

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

Fragment N36: Variable Confusion

struct Status {
  unsigned Mask;
  unsigned Mode;

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

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

Προειδοποίηση PVS-Studio: V1001 [CWE-563] Η μεταβλητή 'Mode' εκχωρείται αλλά δεν χρησιμοποιείται μέχρι το τέλος της συνάρτησης. SIModeRegister.cpp 48

Είναι πολύ επικίνδυνο να δίνουμε στα ορίσματα συναρτήσεων τα ίδια ονόματα με τα μέλη της κλάσης. Είναι πολύ εύκολο να μπερδευτείς. Έχουμε ακριβώς μια τέτοια περίπτωση μπροστά μας. Αυτή η έκφραση δεν έχει νόημα:

Mode &= Mask;

Το όρισμα συνάρτησης αλλάζει. Αυτό είναι όλο. Αυτό το επιχείρημα δεν χρησιμοποιείται πλέον. Πιθανότατα θα έπρεπε να το είχες γράψει έτσι:

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

Fragment N37: Variable Confusion

class SectionBase {
  ....
  uint64_t Size = 0;
  ....
};

class SymbolTableSection : public SectionBase {
  ....
};

void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type,
                                   SectionBase *DefinedIn, uint64_t Value,
                                   uint8_t Visibility, uint16_t Shndx,
                                   uint64_t Size) {
  ....
  Sym.Value = Value;
  Sym.Visibility = Visibility;
  Sym.Size = Size;
  Sym.Index = Symbols.size();
  Symbols.emplace_back(llvm::make_unique<Symbol>(Sym));
  Size += this->EntrySize;
}

Προειδοποίηση PVS-Studio: V1001 [CWE-563] Η μεταβλητή «Μέγεθος» εκχωρείται αλλά δεν χρησιμοποιείται μέχρι το τέλος της συνάρτησης. Αντικείμενο.cpp 424

Η κατάσταση είναι παρόμοια με την προηγούμενη. Θα πρέπει να γραφτεί:

this->Size += this->EntrySize;

Θραύσμα N38-N47: Ξέχασαν να ελέγξουν το ευρετήριο

Προηγουμένως, εξετάσαμε παραδείγματα διαγνωστικής ενεργοποίησης V595. Η ουσία του είναι ότι ο δείκτης δεν αναφέρεται στην αρχή και μόνο τότε ελέγχεται. Νεαρά διαγνωστικά V1004 έχει το αντίθετο νόημα, αλλά αποκαλύπτει και πολλά λάθη. Προσδιορίζει καταστάσεις όπου ο δείκτης ελέγχθηκε στην αρχή και στη συνέχεια ξεχάστηκε να το κάνει. Ας δούμε τέτοιες περιπτώσεις που βρέθηκαν μέσα στο 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());  // <=
  ....
}

Προειδοποίηση PVS-Studio: V1004 [CWE-476] Ο δείκτης 'Ptr' χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Ελέγξτε τις γραμμές: 729, 738. TargetTransformInfoImpl.h 738

Μεταβλητή Ptr μπορεί να είναι ίσο nullptr, όπως αποδεικνύεται από τον έλεγχο:

if (Ptr != nullptr)

Ωστόσο, κάτω από αυτόν τον δείκτη παραπέμπεται χωρίς προκαταρκτικό έλεγχο:

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

Ας εξετάσουμε μια άλλη παρόμοια περίπτωση.

llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
                                                          bool Stub) {
  ....
  auto *FD = dyn_cast<FunctionDecl>(GD.getDecl());
  SmallVector<QualType, 16> ArgTypes;
  if (FD)                                                                // <=
    for (const ParmVarDecl *Parm : FD->parameters())
      ArgTypes.push_back(Parm->getType());
  CallingConv CC = FD->getType()->castAs<FunctionType>()->getCallConv(); // <=
  ....
}

Προειδοποίηση PVS-Studio: V1004 [CWE-476] Ο δείκτης 'FD' χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Ελέγξτε τις γραμμές: 3228, 3231. CGDebugInfo.cpp 3231

Δώστε προσοχή στο σημάδι FD. Είμαι βέβαιος ότι το πρόβλημα είναι καθαρά ορατό και δεν απαιτείται ειδική εξήγηση.

Και επιπλέον:

static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
                                         Value *&BasePtr,
                                         const DataLayout &DL) {
  PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType());
  if (!PtrTy) {                                                   // <=
    Result = Polynomial();
    BasePtr = nullptr;
  }
  unsigned PointerBits =
      DL.getIndexSizeInBits(PtrTy->getPointerAddressSpace());     // <=
  ....
}

Προειδοποίηση PVS-Studio: V1004 [CWE-476] Ο δείκτης 'PtrTy' χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Έλεγχος γραμμών: 960, 965. InterleavedLoadCombinePass.cpp 965

Πώς να προστατευτείτε από τέτοια λάθη; Να είστε πιο προσεκτικοί στο Code-Review και να χρησιμοποιείτε τον στατικό αναλυτή PVS-Studio για να ελέγχετε τακτικά τον κώδικά σας.

Δεν έχει νόημα να αναφέρουμε άλλα τμήματα κώδικα με σφάλματα αυτού του τύπου. Θα αφήσω μόνο μια λίστα προειδοποιήσεων στο άρθρο:

  • V1004 [CWE-476] Ο δείκτης «Expr» χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Έλεγχος γραμμών: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] Ο δείκτης 'PI' χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Ελέγξτε τις γραμμές: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] Ο δείκτης «StatepointCall» χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Γραμμές ελέγχου: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] Ο δείκτης 'RV' χρησιμοποιήθηκε με μη ασφαλή τρόπο αφού επαληθεύτηκε έναντι του nullptr. Έλεγχος γραμμών: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] Ο δείκτης 'CalleeFn' χρησιμοποιήθηκε μη ασφαλώς αφού επαληθεύτηκε έναντι του nullptr. Ελέγξτε τις γραμμές: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] Ο δείκτης 'TC' χρησιμοποιήθηκε με μη ασφαλή τρόπο αφού επαληθεύτηκε έναντι του nullptr. Γραμμές ελέγχου: 1819, 1824. Driver.cpp 1824

Θραύσμα N48-N60: Δεν είναι κρίσιμο, αλλά ένα ελάττωμα (πιθανή διαρροή μνήμης)

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

Προειδοποίηση PVS-Studio: V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Στρατηγικές" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-isel-fuzzer.cpp 58

Για να προσθέσετε ένα στοιχείο στο τέλος ενός κοντέινερ όπως std:: vector > δεν μπορείς απλά να γράφεις xxx.push_back(νέο X), αφού δεν υπάρχει σιωπηρή μετατροπή από X* в std::unique_ptr.

Μια κοινή λύση είναι η γραφή xxx.emplace_back(νέο X)αφού μεταγλωττίζει: μέθοδος emplace_back κατασκευάζει ένα στοιχείο απευθείας από τα ορίσματά του και επομένως μπορεί να χρησιμοποιήσει ρητούς κατασκευαστές.

Δεν είναι ασφαλές. Εάν το διάνυσμα είναι γεμάτο, τότε η μνήμη εκχωρείται εκ νέου. Η λειτουργία ανακατανομής μνήμης μπορεί να αποτύχει, με αποτέλεσμα τη δημιουργία εξαίρεσης std::bad_alloc. Σε αυτήν την περίπτωση, ο δείκτης θα χαθεί και το αντικείμενο που δημιουργήθηκε δεν θα διαγραφεί ποτέ.

Μια ασφαλής λύση είναι η δημιουργία μοναδικό_ptrστην οποία θα ανήκει ο δείκτης πριν το διάνυσμα προσπαθήσει να ανακατανείμει τη μνήμη:

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

Από την C++14, μπορείτε να χρησιμοποιήσετε το 'std::make_unique':

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

Αυτός ο τύπος ελαττώματος δεν είναι κρίσιμος για το LLVM. Εάν δεν μπορεί να εκχωρηθεί μνήμη, ο μεταγλωττιστής απλώς θα σταματήσει. Ωστόσο, για εφαρμογές με μακρύ χρόνο λειτουργίας, το οποίο δεν μπορεί απλώς να τερματιστεί εάν αποτύχει η εκχώρηση μνήμης, αυτό μπορεί να είναι ένα πραγματικό δυσάρεστο σφάλμα.

Έτσι, αν και αυτός ο κώδικας δεν αποτελεί πρακτική απειλή για το LLVM, θεώρησα χρήσιμο να μιλήσω για αυτό το μοτίβο σφάλματος και ότι ο αναλυτής PVS-Studio έχει μάθει να το αναγνωρίζει.

Άλλες προειδοποιήσεις αυτού του τύπου:

  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Passes" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. PassManager.h 546
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "AAs" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. AliasAnalysis.h 324
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Entries' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'AllEdges' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. CFGMST.h 268
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'VMaps' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Records' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. FDRLogBuilder.h 30
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'PendingSubmodules' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. ModuleMap.cpp 810
  • V1023 [CWE-460] Ένας δείκτης χωρίς ιδιοκτήτη προστίθεται στο κοντέινερ 'Objects' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. DebugMap.cpp 88
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Strategies' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 685
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 686
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 688
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 689
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 690
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 691
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 692
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 693
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ "Modifiers" με τη μέθοδο "emplace_back". Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. llvm-stress.cpp 694
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Operands' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Stash' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] Ένας δείκτης χωρίς κάτοχο προστίθεται στο κοντέινερ 'Matchers' με τη μέθοδο 'emplace_back'. Σε περίπτωση εξαίρεσης θα προκύψει διαρροή μνήμης. GlobalISelEmitter.cpp 2702

Συμπέρασμα

Έδωσα 60 ​​προειδοποιήσεις συνολικά και μετά σταμάτησα. Υπάρχουν άλλα ελαττώματα που εντοπίζει ο αναλυτής PVS-Studio στο LLVM; Ναι έχω. Ωστόσο, όταν έγραφα αποσπάσματα κώδικα για το άρθρο, ήταν αργά το βράδυ, ή μάλλον ακόμη και νύχτα, και αποφάσισα ότι ήταν ώρα να το ονομάσω μέρα.

Ελπίζω να σας φάνηκε ενδιαφέρον και θα θέλετε να δοκιμάσετε τον αναλυτή PVS-Studio.

Μπορείτε να κατεβάσετε τον αναλυτή και να λάβετε το κλειδί ναρκαλιευτή στο αυτή τη σελίδα.

Το πιο σημαντικό είναι να χρησιμοποιείτε τακτικά στατική ανάλυση. Εφάπαξ έλεγχοι, που πραγματοποιήθηκε από εμάς προκειμένου να διαδοθεί η μεθοδολογία της στατικής ανάλυσης και το PVS-Studio δεν είναι ένα φυσιολογικό σενάριο.

Καλή τύχη στη βελτίωση της ποιότητας και της αξιοπιστίας του κωδικού σας!

Εύρεση σφαλμάτων στο LLVM 8 χρησιμοποιώντας τον αναλυτή PVS-Studio

Εάν θέλετε να μοιραστείτε αυτό το άρθρο με ένα αγγλόφωνο κοινό, χρησιμοποιήστε τον σύνδεσμο μετάφρασης: Andrey Karpov. Εύρεση σφαλμάτων στο LLVM 8 με το PVS-Studio.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο