Überprüfen von Clang 11 mit PVS-Studio

(28. Oktober 2020) )

Hin und wieder müssen wir Artikel darüber schreiben, wie wir eine andere neue Version eines Compilers überprüft haben. Das macht nicht wirklich viel Spaß. Wie die Praxis zeigt, zweifeln die Leute daran, ob PVS-Studio den Titel eines guten Fängers von Fehlern und Schwachstellen wert ist, wenn wir dies für eine Weile einstellen. Was ist, wenn der neue Compiler das auch kann? Sicher, Compiler entwickeln sich weiter, aber auch PVS-Studio – und es beweist immer wieder seine Fähigkeit, Fehler auch in hochwertigen Projekten wie Compilern zu erkennen.

Zeit für eine erneute Überprüfung von Clang

Um die Wahrheit zu sagen, habe ich diesen Artikel basierend geschrieben im vorherigen Beitrag „ Überprüfen des GCC 10-Compilers mit PVS-Studio „. Wenn Ihnen einige Absätze bekannt vorkommen, liegt dies daran, dass Sie sie bereits zuvor gelesen haben :).

Es ist kein Geheimnis, dass Compiler ihre eigenen integrierten statischen Code-Analysatoren verwenden, und diese entwickeln sich ebenfalls. Deshalb schreiben wir ab und zu Artikel, um zu zeigen, dass unser statischer Analysator PVS-Studio selbst in Compilern Fehler finden kann und dass wir unser Salz wert sind :).

Tatsächlich können Sie das nicht Vergleichen Sie klassische statische Analysatoren mit Compilern. Statische Analysegeräte erkennen nicht nur Fehler im Quellcode, sondern umfassen auch eine hochentwickelte Infrastruktur. Zum einen umfasst es die Integration in Systeme wie SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins und Visual Studio. Es enthält Mechanismen zur Massenunterdrückung von Warnungen, mit denen Sie PVS-Studio auch in großen Projekten sofort verwenden können. Es beinhaltet das Senden von Benachrichtigungen per E-Mail. Und so weiter und so fort. Die erste Frage, die Entwickler noch stellen werden, lautet jedoch: „Kann Ihr PVS-Studio etwas finden, was Compiler nicht finden können?“ Das bedeutet, dass wir dazu verdammt sind, Artikel darüber zu schreiben, wie wir die Compiler selbst immer wieder überprüfen.

Kehren wir zu Clang zurück. Sie müssen sich nicht mit dem Thema befassen und Ihnen sagen, worum es bei dem Projekt geht. Tatsächlich haben wir nicht nur den Code von Clang 11 selbst überprüft, sondern auch den Code der LLVM 11-Bibliothek, auf der er basiert. Aus der Sicht dieses Artikels spielt es keine Rolle, ob ein Fehler im Code des Compilers oder der Bibliothek gefunden wurde.

Ich fand den Code von Clang / LLVM viel klarer als den von GCC. Zumindest wimmelt es nicht von all diesen schrecklichen Makros und es werden weitgehend die modernen Funktionen von C ++ verwendet.

Trotzdem ist das Projekt immer noch groß genug, um die Prüfung des Analyseberichts ohne vorherige Anpassung mühsam zu machen. Was meistens im Weg steht, sind „halb-falsche“ Positive. Mit „halb-falsch“ -Positiven meine ich Fälle, in denen der Analysator technisch korrekt ist, um auf bestimmte Probleme hinzuweisen, diese Warnungen jedoch keinen praktischen Nutzen haben. Viele dieser Warnungen beziehen sich beispielsweise auf Komponententests und generierten Code.

Hier ist ein Beispiel für Komponententests:

Spaces.SpacesInParentheses = false; // <=
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("Type *A = ( Type * )P;", Spaces);
verifyFormat("Type *A = ( vector )P;", Spaces);
verifyFormat("x = ( int32 )y;", Spaces);
verifyFormat("int a = ( int )(2.0f);", Spaces);
verifyFormat("#define AA(X) sizeof((( X * )NULL)->a)", Spaces);
verifyFormat("my_int a = ( my_int )sizeof(int);", Spaces);
verifyFormat("#define x (( int )-1)", Spaces);// Run the first set of tests again with:
Spaces.SpacesInParentheses = false; // <=
Spaces.SpaceInEmptyParentheses = true;
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("call(x, y, z);", Spaces);
verifyFormat("call( );", Spaces);

Die Der Analysator warnt uns, dass den Variablen dieselben Werte zugewiesen wurden, die sie bereits haben:

  • V1048 Der Variablen Spaces.SpacesInParentheses wurde derselbe Wert zugewiesen. FormatTest.cpp 11554
  • V1048 Der Variablen „Spaces.SpacesInCStyleCastParentheses“ wurde der gleiche Wert zugewiesen. FormatTest.cpp 11556

Technisch gesehen ist diese Warnung auf den Punkt gebracht und das Snippet muss vereinfacht oder korrigiert werden. Es ist aber auch klar, dass dieser Code in Ordnung ist und es keinen Sinn macht, irgendetwas darin zu reparieren.

Hier ist ein weiteres Beispiel: Der Analysator gibt eine Menge Warnungen für die automatisch generierte Datei Options.inc aus. Sehen Sie sich die „Wand“ des darin enthaltenen Codes an:

Dieser Großteil des Codes löst eine Flut von Warnungen aus:

  • V501 Links und rechts vom Operator == befinden sich identische Unterausdrücke: nullptr == nullptr Options.inc 26
  • V501 Links und rechts vom Operator == befinden sich identische Unterausdrücke: nullptr == nullptr Options.inc 27
  • V501 Dort sind identische Unterausdrücke links und rechts vom Operator ==: nullptr == nullptr Options.inc 28
  • und so weiter – eine Warnung pro Zeile…

Doch das alles ist keine große Sache. kann gelöst werden, indem irrelevante Dateien von der Analyse ausgeschlossen, bestimmte Makros und Funktionen markiert, bestimmte Diagnosetypen unterdrückt usw. werden. Ja, das kann es, aber es ist keine sehr interessante Aufgabe, wenn Sie einen Artikel schreiben. Deshalb habe ich dasselbe getan wie im Artikel über das Überprüfen des GCC-Compilers: Ich habe den Bericht so lange gelesen, bis ich 11 interessante Beispiele für den Artikel gesammelt habe. Warum 11? Ich dachte nur, da es die 11. Version von Clang war, brauchte ich 11 Beispiele :).

11 verdächtige Codefragmente

Snippet 1, Modulo-Operation für 1

Dies ist eine coole! Ich mag solche Fehler!

void Act() override {
....
// If the value type is a vector, and we allow vector select, then in 50%
// of the cases generate a vector select.
if (isa(Val0->getType()) && (getRandom() % 1)) {
unsigned NumElem =
cast(Val0->getType())->getNumElements();
CondTy = FixedVectorType::get(CondTy, NumElem);
}
....
}

PVS-Studio-Diagnosemeldung: V1063 Die Modulo-by-1-Operation ist bedeutungslos. Das Ergebnis ist immer Null. llvm-stress.cpp 631

Der Programmierer verwendet eine Modulo-Operation, um einen zufälligen Wert von 0 oder 1 zu erhalten. Der Wert 1 scheint Entwickler und zu verwirren lassen Sie sie das klassische Anti-Pattern schreiben, in dem die Modulo-Operation für 1 anstelle von 2 ausgeführt wird. Die Operation X% 1 ist bedeutungslos, da sie immer 0 . Dies ist die feste Version:

if (isa(Val0->getType()) && (getRandom() % 2)) {

Die kürzlich hinzugefügte V1063-Diagnose ist schrecklich einfach, funktioniert aber, wie Sie sehen, einwandfrei.

Wir wissen, dass Compiler-Entwickler unsere Arbeit im Auge behalten und unsere Ideen ausleihen. Das ist völlig in Ordnung. Es ist schön zu wissen, dass PVS-Studio die treibende Kraft hinter dem Fortschritt ist . Mal sehen, wie viel Zeit benötigt wird, bis eine ähnliche Diagnose in Clang und GCC angezeigt wird :).

Snippet 2, ein Tippfehler in einer Bedingung

class ReturnValueSlot {
....
bool isNull() const { return !Addr.isValid(); }
....
};static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
const FunctionDecl *F2, unsigned NumParams) {
....
unsigned I1 = 0, I2 = 0;
for (unsigned I = 0; I != NumParams; ++I) {
QualType T1 = NextParam(F1, I1, I == 0);
QualType T2 = NextParam(F2, I2, I == 0);
if (!T1.isNull() && !T1.isNull() && !Context.hasSameUnqualifiedType(T1, T2))
return false;
}
return true;
}

PVS-Studio-Diagnosemeldung: V501 Links und rechts vom befinden sich identische Unterausdrücke & & Operator :! T1.isNull () & &! T1.isNull () SemaOverload.cpp 9493

Die Prüfung ! T1.isNull () wird zweimal durchgeführt. Dies ist offensichtlich ein Tippfehler; Der zweite Teil der Bedingung muss die Variable T2 überprüfen.

Snippet 3, potenzieller Array-Index-out-of- Grenzen

std::vector DeclsLoaded;SourceLocation ASTReader::getSourceLocationForDeclID(GlobalDeclID ID) {
....
unsigned Index = ID - NUM_PREDEF_DECL_IDS; if (Index > DeclsLoaded.size()) {
Error("declaration ID out-of-range for AST file");
return SourceLocation();
} if (Decl *D = DeclsLoaded[Index])
return D->getLocation();
....
}

PVS-Studio-Diagnosemeldung: V557-Array-Überlauf ist möglich. Der Index zeigt über die Array-Grenze hinaus. ASTReader.cpp 7318

Angenommen, das Array speichert ein Element und der Wert der Variablen Index ist ebenfalls 1. Dann die Bedingung (1> 1) ist falsch, und daher wird das Array über seine Grenzen hinaus indiziert. Hier ist die richtige Prüfung:

if (Index >= DeclsLoaded.size()) {

Snippet 4, Argumentbewertungsreihenfolge

void IHexELFBuilder::addDataSections() {
....
uint32_t SecNo = 1;
....
Section = &Obj->addSection(
".sec" + std::to_string(SecNo++), RecAddr,
ELF::SHF_ALLOC | ELF::SHF_WRITE, SecNo);
....
}

PVS-Studio-Diagnosemeldung: V567 Nicht angegebenes Verhalten. Die Reihenfolge der Argumentauswertung ist für die Funktion „addSection“ nicht definiert. Überprüfen Sie die Variable „SecNo“. Object.cpp 1223

Beachten Sie, dass das Argument SecNo zweimal verwendet wird und in der Zwischenzeit erhöht wird. Das Problem ist, dass Sie nicht sagen können, in welcher genauen Reihenfolge die Argumente ausgewertet werden. Das Ergebnis hängt daher von der Compilerversion oder den Kompilierungsparametern ab.

Hier ist ein synthetisches Beispiel, um diesen Punkt zu veranschaulichen:

#include 
int main()
{
int i = 1;
printf("%d, %d\n", i, i++);
return 0;
}

Je nach Compiler kann dieser Code entweder „1, 2“ oder „2, 1“ ausgeben. Ich habe es im Compiler Explorer ausgeführt und die folgenden Ausgaben erhalten:

  • Beim Kompilieren mit Clang 11.0.0 gibt das Programm 1 aus , 1.
  • Beim Kompilieren mit GCC 10.2 gibt das Programm s 2, 1 aus.

Interessanterweise führt dieser einfache Fall dazu, dass Clang eine Warnung ausgibt:

:6:26: warning:
unsequenced modification and access to "i" [-Wunsequenced]
printf("%d, %d\n", i, i++);

Aus irgendeinem Grund wurde diese Warnung jedoch nicht für den tatsächlichen Code ausgegeben. Entweder ist es als nicht sehr praktisch deaktiviert oder dieser Fall ist zu kompliziert, als dass der Compiler damit umgehen könnte.

Snippet 5, eine seltsame doppelte Prüfung

template <class ELFT>
void GNUStyle::printVersionSymbolSection(const ELFFile *Obj,
const Elf_Shdr *Sec) { ....
Expected NameOrErr =
this->dumper()->getSymbolVersionByIndex(Ndx, IsDefault);
if (!NameOrErr) {
if (!NameOrErr) {
unsigned SecNdx = Sec - &cantFail(Obj->sections()).front();
this->reportUniqueWarning(createError(
"unable to get a version for entry " + Twine(I) +
" of SHT_GNU_versym section with index " + Twine(SecNdx) + ": " +
toString(NameOrErr.takeError())));
}
Versions.emplace_back("");
continue;
}
....
}

PVS-Studio-Diagnosemeldung: V571 Wiederkehrende Prüfung. Die Bedingung „if (! NameOrErr)“ wurde bereits in Zeile 4666 überprüft. ELFDumper.cpp 4667

Die zweite Prüfung ist ein Klon der ersten und daher redundant. Vielleicht könnte es sicher entfernt werden. Wahrscheinlicher ist jedoch, dass es einen Tippfehler enthält und eine andere Variable überprüfen sollte.

Snippet 6, potenzielle Nullzeiger-Dereferenzierung

void RewriteObjCFragileABI::RewriteObjCClassMetaData(
ObjCImplementationDecl *IDecl, std::string &Result)
{
ObjCInterfaceDecl *CDecl = IDecl->getClassInterface(); if (CDecl->isImplicitInterfaceDecl()) {
RewriteObjCInternalStruct(CDecl, Result);
} unsigned NumIvars = !IDecl->ivar_empty()
? IDecl->ivar_size()
: (CDecl ? CDecl->ivar_size() : 0);
....
}

PVS-Studio-Diagnosemeldung: V595 Der Zeiger CDecl wurde verwendet, bevor er gegen nullptr überprüft wurde. Prüfzeilen: 5275, 5284. RewriteObjC.cpp 5275

Bei der ersten Prüfung zögert der Entwickler nie, den CDecl -Zeiger zu dereferenzieren:

if (CDecl->isImplicitInterfaceDecl())

Wenn Sie sich den Code jedoch einige Zeilen weiter ansehen, wird klar, dass der Zeiger null sein kann:

(CDecl ? CDecl->ivar_size() : 0)

Die erste Überprüfung sollte wahrscheinlich folgendermaßen aussehen:

if (CDecl && CDecl->isImplicitInterfaceDecl())

Snippet 7, potenzielle Nullzeiger-Dereferenzierung

bool
Sema::InstantiateClass(....)
{
....
NamedDecl *ND = dyn_cast(I->NewDecl);
CXXRecordDecl *ThisContext =
dyn_cast_or_null(ND->getDeclContext());
CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
ND && ND->isCXXInstanceMember());
....
}

PVS-Studio-Diagnosemeldung: V595 Der Zeiger ND wurde verwendet, bevor er überprüft wurde nullptr.Überprüfen Sie die Zeilen: 2803, 2805. SemaTemplateInstantiate.cpp 2803

Dieser Fehler ähnelt dem vorherigen. Es ist gefährlich, einen Zeiger ohne vorherige Überprüfung zu dereferenzieren, wenn sein Wert mithilfe einer dynamischen Typumwandlung erfasst wird. Darüber hinaus bestätigt der nachfolgende Code, dass eine solche Prüfung erforderlich ist.

Snippet 8, eine Funktion, die trotz eines Fehlerzustands

bool VerifyObject(llvm::yaml::Node &N,
std::map Expected) {
....
auto *V = llvm::dyn_cast_or_null(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
}
std::string VS = V->getValue(Tmp).str();
....
}

PVS-Studio-Diagnosemeldung: V1004 Der Zeiger V wurde unsicher verwendet, nachdem er gegen nullptr überprüft wurde. Überprüfen Sie die Zeilen: 61, 65. TraceTests.cpp 65

Der Zeiger V kann ein Nullzeiger sein. Dies ist offensichtlich ein Fehlerzustand, der sogar mit einer Fehlermeldung gemeldet wird. Aber die Funktion läuft einfach weiter, als wäre nichts passiert, und am Ende wird dieser Nullzeiger dereferenziert. Der Programmierer wollte wahrscheinlich, dass die Funktion an diesem Punkt stoppt. In diesem Fall sollte sie wie folgt behoben werden:

auto *V = llvm::dyn_cast_or_null(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
return false;
}
std::string VS = V->getValue(Tmp).str();

Snippet 9, ein Tippfehler

const char *tools::SplitDebugName(const ArgList &Args, const InputInfo &Input,
const InputInfo &Output) {
if (Arg *A = Args.getLastArg(options::OPT_gsplit_dwarf_EQ))
if (StringRef(A->getValue()) == "single")
return Args.MakeArgString(Output.getFilename()); Arg *FinalOutput = Args.getLastArg(options::OPT_o);
if (FinalOutput && Args.hasArg(options::OPT_c)) {
SmallString<128> T(FinalOutput->getValue());
llvm::sys::path::replace_extension(T, "dwo");
return Args.MakeArgString(T);
} else {
// Use the compilation dir.
SmallString<128> T(
Args.getLastArgValue(options::OPT_fdebug_compilation_dir));
SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput()));
llvm::sys::path::replace_extension(F, "dwo");
T += F;
return Args.MakeArgString(F); // <=
}
}

PVS-Studio-Diagnosemeldung: V1001 Das T Variable wird zugewiesen, aber am Ende der Funktion nicht verwendet. CommonArgs.cpp 873

Sehen Sie sich die letzten Zeilen der Funktion an. Die lokale Variable T ändert sich, wird jedoch in keiner Weise verwendet. Dies muss ein Tippfehler sein und die Funktion sollte wahrscheinlich wie folgt enden:

T += F;
return Args.MakeArgString(T);

Snippet 10, Null als der Divisor

typedef int32_t si_int;
typedef uint32_t su_int;typedef union {
du_int all;
struct {
#if _YUGA_LITTLE_ENDIAN
su_int low;
su_int high;
#else
su_int high;
su_int low;
#endif // _YUGA_LITTLE_ENDIAN
} s;
} udwords;COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem) {
....
if (d.s.low == 0) {
if (d.s.high == 0) {
// K X
// ---
// 0 0
if (rem)
*rem = n.s.high % d.s.low;
return n.s.high / d.s.low;
}
....
}

PVS-Studio-Diagnosemeldungen:

  • V609 Mod um Null. Nenner d.s.low == 0. udivmoddi4.c 61
  • V609 Teilen durch Null. Nenner d.s.low == 0. udivmoddi4.c 62

Ich weiß nicht, ob dies ein Fehler oder eine knifflige Erfindung ist, aber der Code sieht seltsam aus. Es hat zwei reguläre ganzzahlige Variablen, von denen eine durch die andere geteilt wird. Der interessante Teil ist jedoch, dass die Divisionsoperation nur stattfindet, wenn beide Variablen Nullen sind. Welche Aufgabe soll es erfüllen?

Snippet 11, Kopieren und Einfügen

bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(....)
{
....
StringRef FName = II->getName();
....
if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
} if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
}
....
}

PVS-Studio-Diagnosemeldung: V581 Die bedingten Ausdrücke der nebeneinander angeordneten if-Anweisungen sind identisch. Überprüfen Sie die Zeilen: 3108, 3113. MallocChecker.cpp 3113

Ein Codefragment wurde geklont, danach jedoch nie mehr geändert. Dieser Klon sollte entweder entfernt oder geändert werden, um eine nützliche Überprüfung durchzuführen.

Schlussfolgerung

Denken Sie daran, dass Sie diese kostenlose Lizenz verwenden können Option zum Überprüfen von Open Source-Projekten. Wir bieten auch andere Möglichkeiten, PVS-Studio kostenlos zu nutzen. Einige von ihnen ermöglichen sogar die Analyse von proprietärem Code. Die vollständige Liste der Optionen finden Sie hier: „ Möglichkeiten, eine kostenlose PVS-Studio-Lizenz zu erhalten „. Vielen Dank für das Lesen!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.