Seit der letzten Überprüfung des LLVM-Projektcodes mit unserem PVS-Studio-Analysegerät sind mehr als zwei Jahre vergangen. Stellen wir sicher, dass der PVS-Studio-Analysator immer noch das führende Tool zum Erkennen von Fehlern und potenziellen Schwachstellen ist. Überprüfen und überprüfen Sie dazu neue Fehler in der Version LLVM 8.0.0.
Artikel geschrieben werden
Um ehrlich zu sein, wollte ich diesen Artikel nicht schreiben. Es ist nicht interessant, über ein Projekt zu schreiben, das wir bereits wiederholt getestet haben (
1 ,
2 ,
3 ). Es ist besser, über etwas Neues zu schreiben, aber ich habe keine Wahl.
Jedes Mal, wenn eine neue Version von LLVM veröffentlicht oder
Clang Static Analyzer aktualisiert wird, erscheinen die folgenden Fragen in unserer E-Mail:
Schauen Sie, die neue Version von Clang Static Analyzer hat gelernt, neue Fehler zu finden! Es scheint mir, dass die Relevanz der Verwendung von PVS-Studio abnimmt. Clang findet mehr Fehler als zuvor und holt die Funktionen von PVS-Studio ein. Was denkst du darüber?Darauf möchte ich immer etwas im Geiste beantworten:
Wir sitzen auch nicht untätig! Wir haben die Funktionen des PVS-Studio-Analysators erheblich verbessert. Also keine Sorge, wir führen weiter wie bisher.
Leider ist dies eine schlechte Antwort. Es gibt keine Beweise darin. Und deshalb schreibe ich jetzt diesen Artikel. Daher wurde das LLVM-Projekt erneut getestet und es wurden verschiedene Fehler gefunden. Diejenigen, die mir interessant erschienen, werde ich jetzt demonstrieren. Diese Fehler können vom Clang Static Analyzer nicht gefunden werden (oder es ist äußerst unpraktisch, dies zu tun). Und wir können. Und ich habe all diese Fehler an einem Abend gefunden und aufgeschrieben.
Aber das Schreiben des Artikels dauerte mehrere Wochen. Ich konnte mich nicht zwingen, dies alles in Form von Text zu arrangieren :).
Übrigens, wenn Sie daran interessiert sind, welche Technologien im PVS-Studio-Analysegerät verwendet werden, um Fehler und potenzielle Schwachstellen zu erkennen, sollten Sie
sich mit diesem
Hinweis vertraut machen.
Neue und alte Diagnose
Wie bereits erwähnt, wurde das LLVM-Projekt vor etwa zwei Jahren erneut verifiziert und die gefundenen Fehler korrigiert. In diesem Artikel wird nun ein neuer Teil der Fehler vorgestellt. Warum wurden neue Fehler gefunden? Dafür gibt es 3 Gründe:
- Das LLVM-Projekt entwickelt sich, der alte Code ändert sich darin und ein neuer wird angezeigt. Natürlich gibt es neue Fehler im geänderten und geschriebenen Code. Dies zeigt gut, dass die statische Analyse regelmäßig und nicht von Fall zu Fall angewendet werden sollte. Unsere Artikel zeigen die Funktionen des PVS-Studio-Analysators gut, dies hat jedoch nichts mit der Verbesserung der Codequalität und der Reduzierung der Kosten für die Fehlerbehebung zu tun. Verwenden Sie regelmäßig einen statischen Code-Analysator!
- Wir schließen die bestehende Diagnose ab und verbessern sie. Daher kann der Analysator Fehler erkennen, die bei früheren Überprüfungen nicht bemerkt wurden.
- PVS-Studio führte neue Diagnosen ein, die noch keine 2 Jahre alt waren. Ich habe beschlossen, sie in einen separaten Abschnitt zu unterteilen, um die Entwicklung von PVS-Studio klar zu demonstrieren.
Durch Diagnose diagnostizierte Fehler, die vor 2 Jahren bestanden haben
Fragment N1: Kopieren-Einfügenstatic bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) { if (Name == "addcarryx.u32" ||
PVS-Studio Warnung:
V501 [CWE-570] Es gibt identische Unterausdrücke 'Name.startswith ("avx512.mask.permvar.")' Links und rechts vom '||' Betreiber. AutoUpgrade.cpp 73
Es wird doppelt überprüft, ob der Name mit der Teilzeichenfolge "avx512.mask.permvar" beginnt. Im zweiten Test wollten sie eindeutig etwas anderes schreiben, vergaßen jedoch, den kopierten Text zu korrigieren.
Fragment N2: Tippfehler 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 Warnung: V501 Links und rechts vom '|' befinden sich identische Unterausdrücke 'CXNameRange_WantQualifier'. Betreiber. CIndex.cpp 7245
Aufgrund eines Tippfehlers wird die gleichnamige Konstante
CXNameRange_WantQualifier zweimal verwendet .
Snippet N3: Verwirrung über die Prioritäten des Bedieners int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) { .... if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0) return 0; .... }
PVS-Studio Warnung:
V502 [CWE-783] Möglicherweise funktioniert der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '=='. PPCTargetTransformInfo.cpp 404
Meiner Meinung nach ist dies ein sehr schöner Fehler. Ja, ich weiß, dass ich seltsame Vorstellungen von Schönheit habe :).
Entsprechend den
Prioritäten der Operatoren wird der Ausdruck nun wie folgt berechnet:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Aus praktischer Sicht ist diese Bedingung nicht sinnvoll, da sie reduziert werden kann auf:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Dies ist ein klarer Fehler. Höchstwahrscheinlich wollte 0/1 mit der Indexvariablen vergleichen. Fügen Sie Klammern um den ternären Operator hinzu, um den Code zu korrigieren:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Der ternäre Operator ist übrigens sehr gefährlich und provoziert logische Fehler. Seien Sie sehr vorsichtig damit und seien Sie nicht gierig, Klammern zu setzen. Ich habe dieses Thema
hier im Kapitel „Fürchte den Operator?: Und füge es in Klammern ein“ ausführlicher behandelt.
Fragment N4, N5: Nullzeiger 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 Warnung:
V522 [CWE-476] Es kann zu einer Dereferenzierung des Nullzeigers 'LHS' kommen. TGParser.cpp 2152
Wenn der
LHS- Zeiger null ist, sollte eine Warnung ausgegeben werden. Stattdessen wird jedoch dieser Nullzeiger dereferenziert:
LHS-> getAsString () .
Dies ist eine sehr typische Situation, wenn der Fehler im Fehlerhandler versteckt ist, da niemand sie testet. Statische Analysegeräte überprüfen den gesamten erreichbaren Code, unabhängig davon, wie oft er verwendet wird. Dies ist ein sehr gutes Beispiel dafür, wie die statische Analyse andere Test- und Fehlerschutztechniken ergänzt.
Ein ähnlicher Fehler bei der Verarbeitung des
RHS- Zeigers wurde im folgenden Code gemacht: V522 [CWE-476] Eine Dereferenzierung des Nullzeigers 'RHS' kann stattfinden. TGParser.cpp 2186
Fragment N6: Verwenden des Cursors nach dem Bewegen static Expected<bool> ExtractBlocks(....) { .... std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap); .... BD.setNewProgram(std::move(ProgClone));
PVS-Studio Warnung: V522 [CWE-476] Möglicherweise findet eine Dereferenzierung des Nullzeigers 'ProgClone' statt. Miscompilation.cpp 601
Zu Beginn besitzt der
ProgClone- Smart-Zeiger nicht mehr das Objekt:
BD.setNewProgram(std::move(ProgClone));
Tatsächlich ist
ProgClone jetzt ein Nullzeiger. Daher sollte eine Dereferenzierung des Nullzeigers direkt darunter erfolgen:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Aber in der Tat wird dies nicht passieren! Beachten Sie, dass die Schleife nicht ausgeführt wird.
Zu Beginn wird der Container
MiscompiledFunctions gelöscht:
MiscompiledFunctions.clear();
Als nächstes wird die Größe dieses Containers in der Schleifenbedingung verwendet:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Es ist leicht zu erkennen, dass die Schleife nicht startet. Ich denke, das ist auch ein Fehler, und der Code sollte anders geschrieben werden.
Es scheint, dass wir diese sehr berühmte Parität von Fehlern getroffen haben! Ein Fehler verschleiert einen anderen :).
Fragment N7: Verwenden des Cursors nach dem Bewegen 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));
PVS-Studio Warnung: V522 [CWE-476] Es kann zu einer Dereferenzierung des Nullzeigers 'Test' kommen. Miscompilation.cpp 709
Wieder die gleiche Situation. Zuerst wird der Inhalt des Objekts verschoben, und dann wird es so verwendet, als wäre nichts passiert. Ich sehe diese Situation zunehmend im Programmcode, nachdem die Semantik der Bewegung in C ++ erschienen ist. Dafür liebe ich die C ++ Sprache! Es gibt immer mehr neue Möglichkeiten, um sein eigenes Bein zu schießen. PVS-Studio Analyzer funktioniert immer :).
Fragment N8: Nullzeiger 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 Warnung: V522 [CWE-476] Es kann zu einer Dereferenzierung des Nullzeigers 'Typ' kommen. PrettyFunctionDumper.cpp 233
Zusätzlich zu den Fehlerbehandlungsroutinen werden Funktionen zum Debuggen von Ausdrucken normalerweise nicht getestet. Vor uns liegt genau so ein Fall. Die Funktion wartet auf einen Benutzer, der gezwungen ist, das Problem zu beheben, anstatt es zu lösen.
Richtig:
if (Type) Type->dump(*this); else Printer << "<unknown-type>";
Fragment N9: Nullzeiger void SearchableTableEmitter::collectTableEntries( GenericTable &Table, const std::vector<Record *> &Items) { .... RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); if (!Ty)
PVS-Studio Warnung: V522 [CWE-476] Es kann zu einer Dereferenzierung des Nullzeigers 'Ty' kommen. SearchableTableEmitter.cpp 614
Ich denke, und so ist alles klar und bedarf keiner Erklärung.
Fragment N10: Tippfehler bool FormatTokenLexer::tryMergeCSharpNullConditionals() { .... auto &Identifier = *(Tokens.end() - 2); auto &Question = *(Tokens.end() - 1); .... Identifier->ColumnWidth += Question->ColumnWidth; Identifier->Type = Identifier->Type;
PVS-Studio Warnung:
V570 Die Variable 'Identifier-> Type' wird sich selbst zugewiesen. FormatTokenLexer.cpp 249
Es macht keinen Sinn, sich selbst eine Variable zuzuweisen. Höchstwahrscheinlich wollten sie schreiben:
Identifier->Type = Question->Type;
Fragment N11: Verdächtige Pause 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 Warnung:
V622 [CWE-478] Überprüfen Sie die Anweisung 'switch'. Möglicherweise fehlt der erste 'case'-Operator. SystemZAsmParser.cpp 652
Am Anfang steht eine sehr verdächtige
Pausenerklärung . Hast du vergessen, hier etwas anderes zu schreiben?
Fragment N12: Überprüfen des Zeigers nach der Dereferenzierung 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 Warnung:
V595 [CWE-476] Der 'Callee'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 172, 174. AMDGPUInline.cpp 172
Der
Callee- Zeiger am Anfang wird dereferenziert, wenn die Funktion
getTTI aufgerufen wird .
Und dann stellt sich heraus, dass dieser Zeiger auf
nullptr- Gleichheit überprüft werden
sollte :
if (!Callee || Callee->isDeclaration())
Aber es ist zu spät ...
Fragment N13 - N ...: Überprüfen des Zeigers nach der DereferenzierungDie im vorherigen Codefragment beschriebene Situation ist nicht eindeutig. Sie ist hier zu finden:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B, bool isBinary, bool isPrecise = false) { .... Function *CalleeFn = CI->getCalledFunction(); StringRef CalleeNm = CalleeFn->getName();
PVS-Studio Warnung: V595 [CWE-476] Der 'CalleeFn'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 1079, 1081. SimplifyLibCalls.cpp 1079
Und hier:
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());
PVS-Studio Warnung: V595 [CWE-476] Der 'ND'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Und hier:
- V595 [CWE-476] Der 'U'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] Der 'ND'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Und dann interessierte es mich nicht mehr, die Warnungen mit der Nummer V595 zu studieren. Ich weiß also nicht, ob es neben den hier aufgeführten ähnliche Fehler gibt. Höchstwahrscheinlich gibt es.
Fragment N17, N18: Verdächtige Verschiebung static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize, uint64_t &Encoding) { .... unsigned Size = RegSize; .... uint64_t NImms = ~(Size-1) << 1; .... }
PVS-Studio Warnung:
V629 [CWE-190] Überprüfen Sie den Ausdruck '~ (Größe - 1) << 1'. Bitverschiebung des 32-Bit-Werts mit anschließender Erweiterung auf den 64-Bit-Typ. AArch64AddressingModes.h 260
Vielleicht ist dies kein Fehler, und der Code funktioniert genau wie beabsichtigt. Dies ist jedoch eindeutig ein sehr verdächtiger Ort, der überprüft werden muss.
Angenommen, die Variable
Size ist 16, und dann plant der Autor des Codes, den Wert in der Variablen
NImms abzurufen :
1111111111111111111111111111111111111111111111111111111111111100000000
Tatsächlich ist das Ergebnis jedoch:
000000000000000000000000000000000000111111111111111111111111111111000000
Tatsache ist, dass alle Berechnungen mit dem vorzeichenlosen 32-Bit-Typ erfolgen. Und nur dann wird dieser vorzeichenlose 32-Bit-Typ implizit auf
uint64_t erweitert. In diesem Fall sind die höchstwertigen Bits Null.
Sie können die Situation folgendermaßen beheben:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Ähnliche Situation: V629 [CWE-190] Überprüfen Sie den Ausdruck 'Immr << 6'. Bitverschiebung des 32-Bit-Werts mit anschließender Erweiterung auf den 64-Bit-Typ. AArch64AddressingModes.h 269
Snippet N19: Fehlt sonst ein Schlüsselwort ? void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) { .... if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
PVS-Studio Warnung:
V646 [CWE-670] Überprüfen Sie die Logik der Anwendung. Möglicherweise fehlt das Schlüsselwort "else". AMDGPUAsmParser.cpp 5655
Hier gibt es keinen Fehler. Da der then-Block des ersten
if mit
continue endet, spielt es keine Rolle,
ob ein
else- Schlüsselwort vorhanden ist oder nicht. In jedem Fall funktioniert der Code genauso. Das Fehlen eines
anderen macht den Code jedoch dunkler und gefährlicher. Wenn in Zukunft
weiterhin fortfährt , funktioniert der Code auf völlig andere Weise. Meiner Meinung nach ist es besser, noch
etwas hinzuzufügen.
Fragment N20: Vier Tippfehler des gleichen Typs 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();
PVS-Studio-Warnungen:
- V655 [CWE-480] Die Zeichenfolgen wurden verkettet, aber nicht verwendet. Überprüfen Sie den Ausdruck 'Ergebnis + Name.str ()'. Symbol.cpp 32
- V655 [CWE-480] Die Zeichenfolgen wurden verkettet, aber nicht verwendet. Überprüfen Sie den Ausdruck 'Ergebnis + "(ObjC-Klasse)" + Name.str ()'. Symbol.cpp 35
- V655 [CWE-480] Die Zeichenfolgen wurden verkettet, aber nicht verwendet. Überprüfen Sie den Ausdruck 'Ergebnis + "(ObjC-Klasse EH)" + Name.str ()'. Symbol.cpp 38
- V655 [CWE-480] Die Zeichenfolgen wurden verkettet, aber nicht verwendet. Überprüfen Sie den Ausdruck 'Ergebnis + "(ObjC IVar)" + Name.str ()'. Symbol.cpp 41
Zufällig wird der Operator + anstelle des Operators + = verwendet. Das Ergebnis sind bedeutungslose Designs.
Fragment N21: Undefiniertes Verhalten 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(); } } }
Versuchen Sie, den gefährlichen Code selbst zu finden. Und dies ist ein Bild zur Ablenkung, um die Antwort nicht sofort zu sehen:
PVS-Studio Warnung:
V708 [CWE-758] Es wird eine gefährliche Konstruktion verwendet: 'FeaturesMap [Op] = FeaturesMap.size ()', wobei 'FeaturesMap' zur Klasse 'map' gehört. Dies kann zu undefiniertem Verhalten führen. RISCVCompressInstEmitter.cpp 490
Problemlinie:
FeaturesMap[Op] = FeaturesMap.size();
Wenn das
Op- Element nicht gefunden wird, wird ein neues Element in der Karte erstellt und die Anzahl der Elemente in dieser Karte wird dort geschrieben. Es ist nur nicht bekannt, ob die
Größenfunktion vor oder nach dem Hinzufügen eines neuen Elements aufgerufen wird.
Fragment N22-N24: Neuzuweisungen Error MachOObjectFile::checkSymbolTable() const { .... } else { MachO::nlist STE = getSymbolTableEntry(SymDRI); NType = STE.n_type;
PVS-Studio Warnung:
V519 [CWE-563] Der Variablen 'NType' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 1663, 1664. MachOObjectFile.cpp 1664
Ich denke, hier gibt es keinen wirklichen Fehler. Nur überflüssige wiederholte Zuordnung. Aber immer noch ein Fehler.
Ähnlich:
- V519 [CWE-563] Der Variablen 'B.NDesc' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Prüflinien: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Der Variablen werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 59, 61. coff2yaml.cpp 61
Fragment N25-N27: Weitere NeuzuweisungenBetrachten Sie nun eine etwas andere Neuzuweisungsoption.
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 Warnung: V519 [CWE-563] Der Variablen 'Alignment' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 1158, 1160. LoadStoreVectorizer.cpp 1160
Dies ist ein sehr seltsamer Code, der einen logischen Fehler zu enthalten scheint. Zu Beginn wird der
Ausrichtungsvariablen je nach Bedingung ein Wert zugewiesen. Und dann erfolgt die Zuordnung erneut, jedoch jetzt ohne Überprüfung.
Ähnliche Situationen sind hier zu sehen:
- V519 [CWE-563] Der Variablen 'Effekte' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Der Variablen 'ExpectNoDerefChunk' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 4970, 4973. SemaType.cpp 4973
Fragment N28: Immer eine echte Bedingung 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)
PVS-Studio-
Warnung :
V547 [CWE-571] Der Ausdruck 'nextByte! = 0x90' ist immer wahr. X86DisassemblerDecoder.cpp 379
Überprüfung macht keinen Sinn. Die Variable
nextByte ist immer ungleich
0x90 , was sich aus der vorherigen Prüfung ergibt. Dies ist eine Art logischer Fehler.
Fragment N29 - N ...: Immer wahre / falsche BedingungenDer Analysator gibt viele Warnungen aus, dass der gesamte Zustand (
V547 ) oder ein Teil davon (
V560 ) immer wahr oder falsch ist. Oft sind dies keine wirklichen Fehler, sondern einfach ungenauer Code, das Ergebnis der Bereitstellung von Makros und dergleichen. Trotzdem ist es sinnvoll, all diese Warnungen zu betrachten, da es von Zeit zu Zeit echte logische Fehler gibt. Zum Beispiel ist dieser Code verdächtig:
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 Warnung:
V560 [CWE-570] Ein Teil des bedingten Ausdrucks ist immer falsch: RegNo == 0xe. ARMDisassembler.cpp 939
Die Konstante 0xE ist der Wert 14 im Dezimalsystem. Das Überprüfen von
RegNo == 0xe ist nicht sinnvoll, da bei
RegNo> 13 die Ausführung der Funktion abgeschlossen wird.
Es gab viele andere Warnungen mit den Kennungen V547 und V560, aber wie im Fall von
V595 war ich nicht daran interessiert, diese Warnungen zu untersuchen. Es war schon klar, dass ich genug Material hatte, um einen Artikel zu schreiben :). Daher ist nicht bekannt, wie viele Fehler dieses Typs mit PVS-Studio in LLVM erkannt werden können.
Ich werde ein Beispiel geben, warum es langweilig ist, diese Antworten zu studieren. Der Analysator gibt absolut richtig eine Warnung für den folgenden Code aus. Das ist aber kein Fehler.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons, tok::TokenKind ClosingBraceKind) { bool HasError = false; .... HasError = true; if (!ContinueOnSemicolons) return !HasError; .... }
PVS-Studio-Warnung: V547 [CWE-570] Der Ausdruck '! HasError' ist immer falsch. UnwrappedLineParser.cpp 1635
Fragment N30: Verdächtige Rückkehr 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 Warnung:
V612 [CWE-670] Eine bedingungslose 'Rückkehr' innerhalb einer Schleife. R600OptimizeVectorRegisters.cpp 63
Dies ist entweder ein Fehler oder ein bestimmter Trick, der Programmierern, die den Code lesen, etwas erklären soll. Dieser Entwurf erklärt mir nichts und sieht sehr verdächtig aus. Es ist besser, nicht so zu schreiben :).
Bist du müde Dann Zeit, Tee oder Kaffee zu machen.
Durch neue Diagnose erkannte Fehler
Ich denke, 30 Auslösen alter Diagnosen ist genug. Lassen Sie uns nun sehen, was interessant ist, wenn neue Diagnosen erstellt werden, die nach einer
vorherigen Überprüfung im Analysegerät angezeigt wurden. Insgesamt wurden in dieser Zeit 66 Allzweckdiagnosen zum C ++ - Analysegerät hinzugefügt.
Fragment N31: Nicht erreichbarer Code 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 Warnung:
V779 [CWE-561] Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. ExecutionUtils.cpp 146
Wie Sie sehen können, enden beide Zweige der
if-Anweisung mit einem Aufruf der
return-Anweisung . Dementsprechend wird der
CtorDtorsByPriority- Container niemals
geleert .
Snippet N32: Nicht erreichbarer Code 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);
PVS-Studio Warnung: V779 [CWE-561] Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. LLParser.cpp 835
Eine interessante Situation. Schauen wir uns den Anfang dieses Ortes an:
return ParseTypeIdEntry(SummaryID); break;
Auf den ersten Blick scheint es keinen Fehler zu geben. Es scheint, dass die
break- Anweisung hier überflüssig ist und Sie sie einfach löschen können. Es ist jedoch nicht alles so einfach.
Der Analysator generiert eine Warnung in den Zeilen:
Lex.setIgnoreColonInIdentifiers(false); return false;
In der Tat ist dieser Code nicht erreichbar. Alle Fälle in
switch enden mit einem Aufruf der
return-Anweisung . Und jetzt sieht die sinnlose einsame
Pause nicht mehr so harmlos aus! Vielleicht sollte einer der Zweige mit einer
Pause enden und nicht mit einer
Rückkehr ?
Fragment N33: Accidentalisation von hohen 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 Warnung:
V784 Die Größe der Bitmaske ist kleiner als die Größe des ersten Operanden. Dies führt zum Verlust höherer Bits. RuntimeDyld.cpp 815
Beachten Sie, dass die Funktion
getStubAlignment einen
vorzeichenlosen Typ zurückgibt. Wir berechnen den Wert des Ausdrucks, wenn wir annehmen, dass die Funktion den Wert 8 zurückgibt:
~ (getStubAlignment () - 1)
~ (8u-1)
0xFFFFFFF8u
Beachten Sie nun, dass die
DataSize- Variable einen 64-Bit-Typ ohne Vorzeichen hat. Es stellt sich heraus, dass während der Operation DataSize & 0xFFFFFFF88 alle zweiunddreißig höherwertigen Bits zurückgesetzt werden. Dies ist höchstwahrscheinlich nicht das, was der Programmierer wollte. Ich vermute, dass er berechnen wollte: DataSize & 0xFFFFFFFFFFFFFFFFF8u.
Um den Fehler zu beheben, sollten Sie wie folgt schreiben:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Oder so:
DataSize &= ~(getStubAlignment() - 1ULL);
Fragment N34: Explizite Besetzung fehlgeschlagen 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 Warnung:
V1028 [CWE-190] Möglicher Überlauf. Ziehen Sie in Betracht, Operanden des Operators 'NumElts * Scale' in den Typ 'size_t' umzuwandeln, nicht in das Ergebnis. X86ISelLowering.h 1577
Die explizite Typkonvertierung wird verwendet, um einen Überlauf beim Multiplizieren von Variablen vom Typ
int zu verhindern. Das explizite Gießen hier schützt jedoch nicht vor Überlauf.
Zu Beginn werden die Variablen multipliziert, und erst dann wird das 32-Bit-Multiplikationsergebnis auf den Typ size_t erweitert .Fragment N35: Erfolgloses Kopieren und Einfügen 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()));
V778 [CWE-682] Es wurden zwei ähnliche Codefragmente gefunden. Möglicherweise ist dies ein Tippfehler und die Variable 'Op1' sollte anstelle von 'Op0' verwendet werden. InstCombineCompares.cpp 5507Diese neue interessante Diagnose zeigt Situationen auf, in denen ein Codefragment kopiert wurde und einige Namen sich darin zu ändern begannen, aber nicht an einer Stelle korrigiert wurden.Bitte beachten Sie, dass im zweiten Block Op0 in Op1 geändert wurde . Aber an einem Ort haben sie es nicht behoben. Höchstwahrscheinlich hätte es so geschrieben werden sollen: if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) { I.setOperand(1, ConstantFP::getNullValue(Op1->getType())); return &I; }
Fragment N36: Verwirrung in Variablen struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
PVS-Studio Warnung: V1001 [CWE-563] Die Variable 'Mode' wird zugewiesen, aber am Ende der Funktion nicht verwendet. SIModeRegister.cpp 48Es ist sehr gefährlich, Funktionsargumenten dieselben Namen wie Klassenmitgliedern zu geben. Sehr leicht zu verwirren. Vor uns liegt genau so ein Fall. Dieser Ausdruck macht keinen Sinn: Mode &= Mask;
Das Argument der Funktion ändert sich. Und alle. Dieses Argument wird nicht mehr verwendet. Höchstwahrscheinlich musste man so schreiben: Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
Fragment N37: Verwirrung in Variablen 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 Warnung: V1001 [CWE-563] Die Variable 'Größe' wird zugewiesen, aber am Ende der Funktion nicht verwendet. Object.cpp 424Die Situation ist ähnlich wie in der vorherigen. Es sollte geschrieben werden: this->Size += this->EntrySize;
Fragment N38-N47: Zeiger hat die Überprüfung vergessen.Zuvor haben wir Beispiele für die diagnostische Auslösung von V595 untersucht . Das Wesentliche ist, dass der Zeiger am Anfang dereferenziert und erst dann überprüft wird. Die junge Diagnose des V1004 ist die Umkehrung seiner Bedeutung, erkennt aber auch viele Fehler. Es identifiziert Situationen, in denen der Zeiger zu Beginn überprüft und dann vergessen wurde, dies zu tun. Betrachten Sie solche Fälle innerhalb von LLVM. int getGEPCost(Type *PointeeType, const Value *Ptr, ArrayRef<const Value *> Operands) { .... if (Ptr != nullptr) {
PVS-Studio Warnung: V1004 [CWE-476] Der 'Ptr'-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Prüfzeilen: 729, 738. TargetTransformInfoImpl.h 738Die Ptr- Variable kann nullptr sein , wie aus der Prüfung hervorgeht: if (Ptr != nullptr)
Unterhalb dieses Zeigers wird jedoch ohne vorherige Überprüfung dereferenziert: auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Betrachten Sie einen anderen ähnlichen Fall. llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD, bool Stub) { .... auto *FD = dyn_cast<FunctionDecl>(GD.getDecl()); SmallVector<QualType, 16> ArgTypes; if (FD)
PVS-Studio Warnung: V1004 [CWE-476] Der 'FD'-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 3228, 3231. CGDebugInfo.cpp 3231 Achten Sieauf den Zeiger FD . Ich bin sicher, dass das Problem deutlich sichtbar ist und keine besondere Erklärung erforderlich ist.Und auch: static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result, Value *&BasePtr, const DataLayout &DL) { PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType()); if (!PtrTy) {
PVS-Studio Warnung: V1004 [CWE-476] Der 'PtrTy'-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 960, 965. InterleavedLoadCombinePass.cpp 965Wie schützen Sie sich vor solchen Fehlern? Seien Sie bei der Codeüberprüfung vorsichtig und überprüfen Sie Ihren Code regelmäßig mit dem statischen Analysegerät PVS-Studio.Es macht keinen Sinn, andere Codefragmente mit Fehlern dieses Typs mitzubringen. Ich werde nur eine Liste von Warnungen im Artikel hinterlassen:- V1004 [CWE-476] Der Zeiger 'Expr' wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Der 'PI'-Zeiger wurde unsicher verwendet, nachdem er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] The 'StatepointCall' pointer was used unsafely after it was verified against nullptr. Check lines: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] The 'RV' pointer was used unsafely after it was verified against nullptr. Check lines: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] The 'CalleeFn' pointer was used unsafely after it was verified against nullptr. Check lines: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] The 'TC' pointer was used unsafely after it was verified against nullptr. Check lines: 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 Warnung: V1023 [CWE-460] Ein Zeiger ohne Eigentümer wird durch die Methode 'emplace_back' zum Container 'Strategies' hinzugefügt. Im Ausnahmefall tritt ein Speicherverlust auf. llvm-isel-fuzzer.cpp 58Um ein Element am Ende eines Containers wie std :: vector <std :: unique_ptr <X>> hinzuzufügen, können Sie nicht einfach xxx.push_back (neues X) schreiben , da keine implizite Konvertierung von X * nach std erfolgt: : unique_ptr <X> .Eine übliche Lösung besteht darin, xxx.emplace_back (neues X) zu schreiben , da es kompiliert: Die Methode emplace_back erstellt ein Element direkt aus den Argumenten und kann daher explizite Konstruktoren verwenden.Das ist nicht sicher. Wenn der Vektor voll ist, wird Speicher zugewiesen. Eine Speicherzuweisungsoperation kann fehlschlagen, was dazu führt, dass eine std :: bad_alloc- Ausnahme ausgelöst wird . In diesem Fall geht der Zeiger verloren und das erstellte Objekt wird niemals gelöscht.Eine sichere Lösung besteht darin, unique_ptr zu erstellen , dem der Zeiger gehört, bevor der Vektor versucht, den Speicher neu zuzuweisen: xxx.push_back(std::unique_ptr<X>(new X))
Ab C ++ 14 können Sie 'std :: make_unique' verwenden: xxx.push_back(std::make_unique<X>())
Diese Art von Defekt ist für LLVM nicht kritisch. Wenn kein Speicher zugewiesen werden kann, funktioniert der Compiler einfach nicht mehr. Für Anwendungen mit einer langen Betriebszeit , die nicht einfach enden können, wenn die Speicherzuweisung fehlschlägt, kann dies jedoch ein wirklich böser Fehler sein.Obwohl dieser Code keine praktische Gefahr für LLVM darstellt, fand ich es nützlich, über dieses Fehlermuster zu sprechen und dass der PVS-Studio-Analysator gelernt hat, es zu erkennen.Andere Warnungen dieses Typs:- V1023 [CWE-460] Ein Zeiger ohne Eigentümer wird dem Container 'Passes' durch die Methode 'emplace_back' hinzugefügt. Im Ausnahmefall tritt ein Speicherverlust auf. PassManager.h 546
- V1023 [CWE-460] A pointer without owner is added to the 'AAs' container by the 'emplace_back' method. A memory leak will occur in case of an exception. AliasAnalysis.h 324
- V1023 [CWE-460] A pointer without owner is added to the 'Entries' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] A pointer without owner is added to the 'AllEdges' container by the 'emplace_back' method. A memory leak will occur in case of an exception. CFGMST.h 268
- V1023 [CWE-460] A pointer without owner is added to the 'VMaps' container by the 'emplace_back' method. A memory leak will occur in case of an exception. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] A pointer without owner is added to the 'Records' container by the 'emplace_back' method. A memory leak will occur in case of an exception. FDRLogBuilder.h 30
- V1023 [CWE-460] A pointer without owner is added to the 'PendingSubmodules' container by the 'emplace_back' method. A memory leak will occur in case of an exception. ModuleMap.cpp 810
- V1023 [CWE-460] A pointer without owner is added to the 'Objects' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DebugMap.cpp 88
- V1023 [CWE-460] A pointer without owner is added to the 'Strategies' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 685
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 686
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 688
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 689
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 690
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 691
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 692
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 693
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 694
- V1023 [CWE-460] A pointer without owner is added to the 'Operands' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] A pointer without owner is added to the 'Stash' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] A pointer without owner is added to the 'Matchers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2702
Fazit
Insgesamt schrieb ich 60 Warnungen aus, danach hörte ich auf. Gibt es andere Fehler, die der PVS-Studio-Analysator in LLVM erkennt? Ja, gibt es.
Als ich jedoch die Codefragmente für den Artikel schrieb, war es später Abend oder besser gesagt sogar Nacht, und ich entschied, dass es Zeit war, abzurunden.Ich hoffe, Sie waren interessiert und möchten den PVS-Studio-Analysator ausprobieren.Auf dieser Seite können Sie den Analysator herunterladen und einen Testschlüssel erhalten .Verwenden Sie vor allem regelmäßig statische Analysen. Einmalige Überprüfungen, die von uns durchgeführt werden, um die Methodik der statischen Analyse und von PVS-Studio bekannt zu machen, sind kein normales Szenario.Viel Glück bei der Verbesserung der Qualität und Zuverlässigkeit des Codes!
Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov. Suchen von Fehlern in LLVM 8 mit PVS-Studio .