Überprüfung des LibrePCB-Projekts mit PVS-Studio im Docker-Container

PVS-Studio und Docker Container

Dies ist ein klassischer Artikel darüber, wie unser Team ein offenes LibrePCB-Projekt mit dem statischen Code-Analysator PVS-Studio getestet hat. Der Artikel ist jedoch insofern interessant, als die Überprüfung im Docker-Container durchgeführt wurde. Wenn Sie Container verwenden, hoffen wir, dass der Artikel eine weitere einfache Möglichkeit zur Integration des Analysators in den Entwicklungsprozess zeigt.

LibrePCB


LibrePCB ist eine kostenlose Software zum Entwerfen elektronischer Schaltungen und Leiterplatten. Der Programmcode ist in C ++ geschrieben und Qt5 wird zum Erstellen der grafischen Oberfläche verwendet. Vor kurzem fand die erste offizielle Veröffentlichung dieser Anwendung statt, die die Stabilisierung des eigenen Dateiformats (* .lp, * .lplib) markierte. Binärpakete für Linux, MacOS und Windows.

LibrePCB


LibrePCB ist ein kleines Projekt, das nur etwa 300.000 nicht leere Codezeilen in C und C ++ enthält. Gleichzeitig sind 25% der nicht leeren Zeilen Kommentare. Dies ist übrigens ein großer Prozentsatz für Kommentare. Dies ist höchstwahrscheinlich auf die Tatsache zurückzuführen, dass das Projekt viele kleine Dateien enthält, von denen ein erheblicher Teil mit Überschriftenkommentaren aus den Projektinformationen und der Lizenz belegt ist. GitHub-Site-Code: LibrePCB .

Das Projekt schien uns interessant zu sein, und wir beschlossen, es auszuprobieren. Aber die Testergebnisse waren nicht mehr so ​​interessant. Ja, es gab echte Fehler. Aber es gab nichts Besonderes, worüber wir den Lesern unserer Artikel sicherlich erzählen müssen. Vielleicht würden wir keinen Artikel schreiben und uns darauf beschränken, die Fehler an die Entwickler des Projekts zu senden. Der interessante Punkt war jedoch, dass das Projekt im Docker-Image getestet wurde und wir daher entschieden haben, dass es sich immer noch lohnt, einen Artikel zu schreiben.

Docker


Docker - Software zur Automatisierung der Bereitstellung und Verwaltung von Anwendungen in einer Virtualisierungsumgebung auf Betriebssystemebene. Sie können die Anwendung mit all ihren Umgebungen und Abhängigkeiten in einen Container "packen". Obwohl diese Technologie ungefähr fünf Jahre alt ist und viele Unternehmen Docker schon lange in die Infrastruktur ihrer Projekte implementiert haben, war dies in der Open-Source-Welt bis vor kurzem nicht sehr auffällig.

Unser Unternehmen arbeitet sehr eng mit Open Source-Projekten zusammen und überprüft den Quellcode mit unserem eigenen statischen PVS-Studio-Analysegerät. Derzeit wurden mehr als 300 Projekte verifiziert. Das Schwierigste in dieser Aktivität war immer die Zusammenstellung von Projekten anderer Leute, aber die Einführung von Docker-Containern hat diesen Prozess erheblich vereinfacht.

Die erste Erfahrung beim Testen eines Open Source-Projekts in Docker machte Azure Service Fabric . Dort haben die Entwickler die Installation des Quelldateiverzeichnisses im Container vorgenommen, und die Integration des Analysators beschränkte sich auf die Bearbeitung eines der im Container ausgeführten Skripte:

diff --git a/src/build.sh b/src/build.sh index 290c57d..2a286dc 100755 --- a/src/build.sh +++ b/src/build.sh @@ -193,6 +193,9 @@ BuildDir() cd ${ProjBinRoot}/build.${DirName} + pvs-studio-analyzer analyze --cfg /src/PVS-Studio.cfg \ + -o ./service-fabric-pvs.log -j4 + if [ "false" = ${SkipBuild} ]; then if (( $NumProc <= 0 )); then NumProc=$(($(getconf _NPROCESSORS_ONLN)+0)) 

Der Unterschied zwischen dem LibrePCB-Projekt besteht darin, dass sofort die Docker-Datei zum Erstellen des Images und des darin enthaltenen Projekts bereitgestellt wurde. Es stellte sich für uns als noch bequemer heraus. Hier ist der Teil der Docker-Datei, an dem wir interessiert sind:

 FROM ubuntu:14.04 # install packages RUN DEBIAN_FRONTEND=noninteractive \ apt-get -q update \ && apt-get -qy upgrade \ && apt-get -qy install git g++ qt5-default qttools5-dev-tools qt5-doc \ qtcreator libglu1-mesa-dev dia \ && apt-get clean # checkout librepcb RUN git clone --recursive https://..../LibrePCB.git /opt/LibrePCB \ && cd /opt/LibrePCB .... # build and install librepcb RUN /opt/LibrePCB/dev/docker/make_librepcb.sh .... 

Wir werden das Projekt beim Zusammenstellen des Bildes nicht kompilieren und installieren. So haben wir ein Bild gesammelt, in dem der Autor des Projekts die erfolgreiche Montage des Projekts garantiert.

Nach dem Starten des Containers wurde der Analysator installiert und die folgenden Befehle wurden ausgeführt, um das Projekt zu erstellen und zu analysieren:

 cd /opt/LibrePCB mkdir build && cd build qmake -r ../librepcb.pro pvs-studio-analyzer trace -- make -j2 pvs-studio-analyzer analyze -l /mnt/Share/PVS-Studio.lic -r /opt/LibrePCB \ -o /opt/LibrePCB/LibrePCB.log -v -j4 cp -R -L -a /opt/LibrePCB /mnt/Share 

Übrigens wurden alle Aktionen in Windows 10 ausgeführt. Es ist sehr erfreulich, dass sich auch die Entwickler aller gängigen Betriebssysteme in diese Richtung entwickeln. Leider sind Container mit Windows nicht so bequem. Vor allem wegen der Unmöglichkeit, auch Software einfach zu installieren.

Bugs gefunden


Nun ein klassischer Abschnitt mit einer Beschreibung der Fehler, die wir mit dem statischen Code-Analysator PVS-Studio gefunden haben. Bei dieser Gelegenheit möchte ich Sie übrigens daran erinnern, dass wir kürzlich einen Analysator entwickeln, der die Codeanalyse für eingebettete Systeme unterstützt. Hier sind einige Artikel, die einige unserer Leser möglicherweise übersprungen haben:

  1. PVS-Studio bietet Unterstützung für die GNU Arm Embedded Toolchain .
  2. PVS-Studio: Unterstützung für Codierungsstandards MISRA C und MISRA C ++ .

Tippfehler


 SymbolPreviewGraphicsItem::SymbolPreviewGraphicsItem( const IF_GraphicsLayerProvider& layerProvider, const QStringList& localeOrder, const Symbol& symbol, const Component* cmp, const tl::optional<Uuid>& symbVarUuid, const tl::optional<Uuid>& symbVarItemUuid) noexcept { if (mComponent && symbVarUuid && symbVarItemUuid) .... if (mComponent && symbVarItemUuid && symbVarItemUuid) // <= .... } 

PVS-Studio Warnung: V501 CWE-571 Links und rechts vom Operator '&&' befinden sich identische Unterausdrücke 'symbVarItemUuid'. symbolpreviewgraphicsitem.cpp 74

Klassischer Tippfehler: Die Variable symbVarItemUuid wird zweimal hintereinander überprüft. Es gibt oben eine ähnliche Prüfung, und wenn man sie betrachtet, wird klar, dass die zweite zu prüfende Variable symbVarUuid sein sollte .

Das folgende Tippfehler-Code-Snippet:

 void Clipper::DoMaxima(TEdge *e) { .... if (e->OutIdx >= 0) { AddOutPt(e, e->Top); e->OutIdx = Unassigned; } DeleteFromAEL(e); if (eMaxPair->OutIdx >= 0) { AddOutPt(eMaxPair, e->Top); // <= eMaxPair->OutIdx = Unassigned; } DeleteFromAEL(eMaxPair); .... } 

PVS-Studio Warnung: V778 CWE-682 Es wurden zwei ähnliche Codefragmente gefunden. Möglicherweise ist dies ein Tippfehler und die Variable 'eMaxPair' sollte anstelle von 'e' verwendet werden. clipper.cpp 2999

Höchstwahrscheinlich wurde der Code mit Copy-Paste geschrieben. Aufgrund des Versehens im zweiten Textblock haben sie vergessen, e-> Top durch eMaxPair-> Top zu ersetzen.

Zusätzliche Schecks


 static int rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data) { if (!content || !content->size) return 0; HOEDOWN_BUFPUTSL(ob, "<em>"); if (content) hoedown_buffer_put(ob, content->data, content->size); HOEDOWN_BUFPUTSL(ob, "</em>"); return 1; } 

PVS-Studio Warnung: V547 CWE-571 Der Ausdruck 'Inhalt' ist immer wahr. html.c 162

Dies ist wahrscheinlich immer noch kein Fehler, sondern einfach redundanter Code. Der Inhaltszeiger muss nicht erneut überprüft werden . Wenn es Null ist, beendet die Funktion sofort ihre Arbeit.

Eine ähnliche Situation:

 void Clipper::DoMaxima(TEdge *e) { .... else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) { if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } .... } 

PVS-Studio- Warnung : V547 CWE-571 Ausdruck 'e-> OutIdx> = 0' ist immer wahr. clipper.cpp 2983

Eine erneute Überprüfung (e-> OutIdx> = 0) ist nicht sinnvoll. Vielleicht ist dies jedoch ein Fehler. Zum Beispiel können wir davon ausgehen, dass die Variable e-> Top aktiviert sein sollte. Dies ist jedoch nur eine Vermutung. Wir sind mit dem Projektcode nicht vertraut und können Fehler nicht von redundantem Code unterscheiden :).

Und noch ein Fall:

 QString SExpression::toString(int indent) const { .... if (child.isLineBreak() && nextChildIsLineBreak) { if (child.isLineBreak() && (i > 0) && mChildren.at(i - 1).isLineBreak()) { // too many line breaks ;) } else { str += '\n'; } } .... } 

PVS-Studio Warnung: V571 CWE-571 Wiederkehrende Prüfung. Die Bedingung 'child.isLineBreak ()' wurde bereits in Zeile 208 überprüft. Sexpression.cpp 209

Fehler in der Logik


 void FootprintPreviewGraphicsItem::paint(....) noexcept { .... for (const Circle& circle : mFootprint.getCircles()) { layer = mLayerProvider.getLayer(*circle.getLayerName()); if (!layer) continue; // <= if (layer) { // <= pen = QPen(....); painter->setPen(pen); } else painter->setPen(Qt::NoPen); .... } .... } 

PVS-Studio Warnung: V547 CWE-571 Ausdruck 'Ebene' ist immer wahr. footprintpreviewgraphicsitem.cpp 177

Da die Bedingung in der zweiten if-Anweisung immer wahr ist, ist der else- Zweig niemals erfüllt.

Zeigerprüfung vergessen


 extern int ZEXPORT unzGetGlobalComment ( unzFile file, char * szComment, uLong uSizeBuf) { .... if (uReadThis>0) { *szComment='\0'; if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) return UNZ_ERRNO; } if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) *(szComment+s->gi.size_comment)='\0'; .... } 

PVS-Studio Warnung: V595 CWE-476 Der Zeiger 'szComment' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 2068, 2073. unzip.c 2068

Wenn uReadThis> 0 ist , wird der szComment- Zeiger dereferenziert . Dies ist gefährlich, da dieser Zeiger möglicherweise null ist. Der Analysator zieht eine solche Schlussfolgerung auf der Grundlage der Tatsache, dass dieser Zeiger weiter auf Gleichheit NULL geprüft wird.

Nicht initialisiertes Klassenmitglied


 template <class T> class Edge { public: using VertexType = Vector2<T>; Edge(const VertexType &p1, const VertexType &p2, T w=-1) : p1(p1), p2(p2), weight(w) {}; // <= Edge(const Edge &e) : p1(e.p1), p2(e.p2), weight(e.weight), isBad(false) {}; Edge() : p1(0,0), p2(0,0), weight(0), isBad(false) {} VertexType p1; VertexType p2; T weight=0; bool isBad; }; 

PVS-Studio Warnung: V730 CWE-457 Nicht alle Mitglieder einer Klasse werden im Konstruktor initialisiert. Betrachten Sie Folgendes: isBad. edge.h 14

Alle Konstruktoren außer dem ersten initialisieren das Feld der isBad- Klasse. Höchstwahrscheinlich hat der erste Konstruktor diese Initialisierung einfach versehentlich vergessen. Infolgedessen erstellt der erste Konstruktor ein unvollständig initialisiertes Objekt, was zu undefiniertem Programmverhalten führen kann.

Es gibt 11 weitere V730- Diagnoseauslöser. Da wir mit dem Code jedoch nicht vertraut sind, ist es schwierig zu sagen, ob diese Warnungen auf echte Probleme hinweisen. Ich denke, diese Warnungen werden am besten von den Autoren des Projekts untersucht.

Speicherverlust


 template <typename ElementType> void ProjectLibrary::loadElements(....) { .... ElementType* element = new ElementType(elementDir, false); // can throw if (elementList.contains(element->getUuid())) { throw RuntimeError( __FILE__, __LINE__, QString(tr("There are multiple library elements with the same " "UUID in the directory \"%1\"")) .arg(subdirPath.toNative())); } .... } 

PVS-Studio Warnung: V773 CWE-401 Die Ausnahme wurde ausgelöst, ohne den Zeiger 'Element' freizugeben. Ein Speicherverlust ist möglich. projectlibrary.cpp 245

Wenn ein Element bereits in der Liste enthalten ist, wird eine Ausnahme ausgelöst. Dies zerstört jedoch nicht das zuvor erstellte Objekt, dessen Zeiger in der Elementvariablen gespeichert ist.

Ungültiger Ausnahmetyp


 bool CmdRemoveSelectedSchematicItems::performExecute() { .... throw new LogicError(__FILE__, __LINE__); .... } 

PVS-Studio Warnung: V1022 CWE-755 Eine Ausnahme wurde vom Zeiger ausgelöst. Ziehen Sie es stattdessen nach Wert. cmdremoveselectedschematicitems.cpp 143

Der Analysator hat eine vom Zeiger ausgelöste Ausnahme festgestellt. Meistens ist es üblich, Ausnahmen nach Wert auszulösen und nach Referenz abzufangen. Das Werfen eines Zeigers kann dazu führen, dass die Ausnahme nicht abgefangen wird, da sie als Referenz abgefangen wird. Die Verwendung eines Zeigers zwingt den Interceptor außerdem, den Löschoperator aufzurufen, um das erstellte Objekt zu zerstören, damit keine Speicherverluste auftreten.

Im Allgemeinen wird der neue Operator hier versehentlich geschrieben und sollte gelöscht werden. Dass dies ein Fehler ist, wird durch die Tatsache bestätigt, dass an allen anderen Stellen steht:

 throw LogicError(__FILE__, __LINE__); 

Gefährliche Verwendung von dynamic_cast


 void GraphicsView::handleMouseWheelEvent( QGraphicsSceneWheelEvent* event) noexcept { if (event->modifiers().testFlag(Qt::ShiftModifier)) .... } bool GraphicsView::eventFilter(QObject* obj, QEvent* event) { .... handleMouseWheelEvent(dynamic_cast<QGraphicsSceneWheelEvent*>(event)); .... } 

PVS-Studio Warnung: V522 CWE-628 Es kann zu einer Dereferenzierung des Nullzeiger-Ereignisses kommen. Der potenzielle Nullzeiger wird an die Funktion 'handleMouseWheelEvent' übergeben. Überprüfen Sie das erste Argument. Überprüfen Sie die Zeilen: 143, 252. graphicsview.cpp 143

Der aus dem Operator dynamic_cast resultierende Zeiger wird an die Funktion handleMouseWheelEvent übergeben . Darin wird dieser Zeiger ohne vorherige Überprüfung dereferenziert.

Dies ist gefährlich, da der Operator dynamic_cast möglicherweise nullptr zurückgibt . Es stellt sich heraus, dass dieser Code nicht besser ist als nur die Verwendung des schnelleren static_cast .

Vor der Verwendung sollte diesem Code eine explizite Zeigerprüfung hinzugefügt werden.

Auch Code dieser Art ist sehr verbreitet:

 bool GraphicsView::eventFilter(QObject* obj, QEvent* event) { .... QGraphicsSceneMouseEvent* e = dynamic_cast<QGraphicsSceneMouseEvent*>(event); Q_ASSERT(e); if (e->button() == Qt::MiddleButton) .... } 

PVS-Studio Warnung: V522 CWE-690 Möglicherweise wird ein potenzieller Nullzeiger 'e' dereferenziert. graphicsview.cpp 206

Der Zeiger wird mit dem Makro Q_ASSERT überprüft. Hier ist seine Beschreibung:
Druckt eine Warnmeldung mit dem Namen der Quellcodedatei und der Zeilennummer, wenn der Test falsch ist.

Q_ASSERT () ist nützlich, um Vor- und Nachbedingungen während der Entwicklung zu testen. Es tut nichts, wenn QT_NO_DEBUG während der Kompilierung definiert wurde.

Q_ASSERT ist eine schlechte Methode, um Zeiger vor der Verwendung zu überprüfen. In der Release-Version ist QT_NO_DEBUG in der Regel nicht definiert. Ich weiß nicht, wie es im LibrePCB-Projekt läuft. Wenn jedoch QT_NO_DEBUG in Release definiert ist, ist dies eine seltsame und nicht standardmäßige Lösung.

Wenn sich das Makro in die Leere ausdehnt, stellt sich heraus, dass keine Überprüfung erfolgt. Und dann ist nicht klar, warum Sie dynamic_cast überhaupt verwenden. Warum dann nicht static_cast verwenden ?

Im Allgemeinen ist dieser Code geruchlos und eine Überprüfung aller ähnlichen Codefragmente wert. Und es gibt übrigens viele von ihnen. Ich habe 82 ähnliche Fälle gezählt!

Fazit


Im Allgemeinen schien uns das LibrePCB-Projekt von hoher Qualität zu sein. Wir empfehlen jedoch, dass sich die Autoren des Projekts mit dem PVS-Studio-Tool ausrüsten und unabhängig eine Codeüberprüfung der vom Analysator angegebenen Codeabschnitte durchführen. Wir sind bereit, ihnen einen Monat lang eine kostenlose Lizenz für eine vollständige Analyse des Projekts zur Verfügung zu stellen. Darüber hinaus können sie die kostenlose Lizenzierungsoption für Analysegeräte verwenden, da der Projektcode geöffnet und auf der GitHub-Website veröffentlicht ist. Wir werden in Kürze über diese Lizenzoption schreiben.



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov, Svyatoslav Razmyslov. Überprüfen von LibrePCB mit PVS-Studio in einem Docker-Container .

Source: https://habr.com/ru/post/de433562/


All Articles