2017-11-14 9 views
2

markieren Ich benutze PVS-Studio um meinen Testcode zu analysieren. Es gibt Konstrukte oft von der FormVariable als nicht NULL nach BOOST_REQUIRE in PVS-Studio

const noAnimal* animal = dynamic_cast<noAnimal*>(...); 
BOOST_REQUIRE(animal); 
BOOST_REQUIRE_EQUAL(animal->GetSpecies(), ...); 

aber ich bekomme immer noch eine Warnung V522 There might be dereferencing of a potential null pointer 'animal' für die letzte Zeile.

Ich weiß, es möglich ist, zu markieren, die als „nicht NULL Rückkehr“, sondern ist es auch möglich, eine Funktion als gültige NULL Scheck oder machen PVS-Studio irgendwie sonst bewusst zu markieren, dass animal nicht NULL sein kann nach BOOST_REQUIRE(animal); ?

Dies geschieht auch, wenn der Zeiger zuerst über eine beliebige assert Geschmacksrichtung überprüft wird.

Antwort

1

Vielen Dank für das interessante Beispiel. Wir werden denken, was wir mit dem Makro BOOST_REQUIRE machen können.

Im Moment kann ich Ihnen folgende Lösung beraten:

Irgendwo nach

#include <boost/test/included/unit_test.hpp> 

Sie schreiben:

#ifdef PVS_STUDIO 
    #undef BOOST_REQUIRE 
    #define BOOST_REQUIRE(expr) do { if (!(expr)) throw "PVS-Studio"; } while (0) 
#endif 

Auf diese Weise werden Sie einen Hinweis auf das geben Analysator, dass die falsche Bedingung den Abbruch des Kontrollflusses verursacht. Es ist nicht die schönste Lösung, aber ich denke, es war es wert, Ihnen davon zu erzählen.

+0

Danke für die Antwort. Obwohl dies möglich ist, wäre es sehr mühsam, dies in allen Testfalldateien zu definieren. Dies ist nicht nur auf "BOOST_REQUIRE" beschränkt, sondern gilt auch für "assert", "SDL_Assert" oder jedes andere benutzerdefinierte Makro, das der Benutzer verwenden könnte. Also sehe ich 2 Lösungen: Entweder lasse der Benutzer eine Liste von "assert" -macro-Namen angeben, nach denen die Bedingung als wahr angenommen wird (da dies der Grund für die Assert ist). – Flamefire

+0

Oder mindestens eine globale, PVS-only-Datei mit Defines, die von PVS gelesen werden und angenommen werden, verwendet zu werden (ähnlich wie der 'cpp.hint'). Sie können also solche Definitionen an einem zentralen Ort platzieren. Ich denke jedoch, dass dies schwierig sein wird, wie es in jeder Datei möglich ist. – Flamefire

1

mit einem großen zu einem Kommentar Reaktion ist eine schlechte Idee, so ist hier meine ausführliche Antwort auf den folgenden Themen:

Obwohl dies möglich ist, es ist ein Schmerz wäre, dass alle in definieren ist Testfalldateien. Auch dies ist nicht auf BOOST_REQUIRE beschränkt, sondern gilt auch für Assert, SDL_Assert oder jedes andere benutzerdefinierte Makro, das der Benutzer verwenden könnte.

Man sollte verstehen, dass es drei Arten von Testmakros gibt, die jeweils separat besprochen werden sollten.

Makros des ersten Typs warnen Sie einfach, dass in der Debug-Version etwas schief gelaufen ist. Ein typisches Beispiel ist assert Makro. Der folgende Code wird dazu führen, PVS-Studio Analyzer eine Warnung zu generieren:

T* p = dynamic_cast<T *>(x); 
assert(p); 
p->foo(); 

Der Analysator wird einen möglichen Nullzeiger weist darauf hin, dereferencing hier und wird richtig sein. Ein Check, der assert verwendet, ist nicht ausreichend, da er aus der Release-Version entfernt wird. Das heißt, es stellt sich heraus, dass es keine Überprüfung gibt. Ein besserer Weg, es zu implementieren ist, den Code in etwa wie folgt zu umschreiben:

T* p = dynamic_cast<T *>(x); 
if (p == nullptr) 
{ 
    assert(false); 
    throw Error; 
} 
p->foo(); 

Dieser Code wird nicht die Warnung auslösen.

Sie können argumentieren, dass Sie 100% sicher sind, dass dynamic_cast niemals nullptr zurückgeben wird. Ich akzeptiere dieses Argument nicht. Wenn Sie absolut sicher sind, dass die Besetzung IMMER korrekt ist, sollten Sie die schnellere static_cast verwenden. Wenn Sie nicht so sicher sind, müssen Sie den Zeiger testen, bevor Sie ihn dereferenzieren.

Nun, OK, ich verstehe Ihren Standpunkt. Sie sind sicher, dass der Code in Ordnung ist, aber Sie müssen diese Überprüfung mit dynamic_cast nur für den Fall haben. OK, verwenden Sie dann den folgenden Code:

assert(dynamic_cast<T *>(x) != nullptr); 
T* p = static_cast<T *>(x); 
p->foo(); 

Ich mag es nicht, aber zumindest ist es schneller, da der langsame dynamic_cast Operator in der Release-Version wird weggelassen wird, während der Analysator schweigen wird.

Weiter mit der nächsten Art von Makros.

Makros des zweiten Typs warnen Sie einfach, dass in der Debug-Version etwas schief gelaufen ist und in Tests verwendet wird. Sie unterscheiden sich vom vorherigen Typ dadurch, dass sie den zu testenden Algorithmus stoppen, wenn die Bedingung falsch ist und eine Fehlermeldung generiert.

Das grundlegende Problem mit diesen Makros besteht darin, dass die Funktionen nicht als nicht wiederkehrend gekennzeichnet sind. Hier ist ein Beispiel.

Angenommen, wir haben eine Funktion, die eine Fehlermeldung generiert, indem sie eine Ausnahme auslöst. Dies ist, was seine Erklärung wie folgt aussieht:

void Error(const char *message); 

Und das ist, wie der Test Makro deklariert wird:

#define ENSURE(x) do { if (!x) Error("zzzz"); } while (0) 

Mit dem Zeiger:

T* p = dynamic_cast<T *>(x); 
ENSURE(p); 
p->foo(); 

Der Analysator wird eine Warnung ausgeben eine mögliche Null-Zeiger-Dereferenzierung, aber der Code ist tatsächlich sicher. Wenn der Zeiger Null ist, löst die Funktion Error eine Ausnahme aus und verhindert so die Dereferenzierung des Zeigers.

Wir müssen einfach den Analysator, darüber sagen durch eine der Funktions Annotation Mittel verwenden, zum Beispiel:

[[noreturn]] void Error(const char *message); 

oder:

__declspec(noreturn) void Error(const char *message); 

Dies wird helfen, die falsche Warnung zu beseitigen. Wie Sie sehen können, ist es in den meisten Fällen recht einfach, Dinge zu beheben, wenn Sie Ihre eigenen Makros verwenden.

Es könnte jedoch schwieriger sein, wenn Sie mit unvorsichtig implementierten Makros aus Bibliotheken von Drittanbietern umgehen.

Dies führt uns zu der dritten Art von Makros. Sie können sie nicht ändern, und der Analysator kann nicht herausfinden, wie genau sie funktionieren. Dies ist eine häufige Situation, da Makros auf ziemlich exotische Weise implementiert werden können.

Es gibt drei Möglichkeiten für Sie in diesem Fall links:

  1. unterdrücken die Warnung eines der falsch-positiven Suppression Mittel, das in den documentation beschrieben;
  2. Verwenden Sie die Technik, die ich in der vorherigen beschrieben answer;
  3. mailen Sie uns.

Wir fügen nach und nach Unterstützung für verschiedene knifflige Makros aus beliebten Bibliotheken hinzu.In der Tat ist der Analysator bereits mit den meisten spezifischen Makros vertraut, auf die Sie stoßen könnten, aber die Vorstellungskraft der Programmierer ist unerschöpflich, und wir können nicht jede mögliche Implementierung vorhersehen.

+0

Ich stimme dir in der Geschichte von dynamic_cast nicht zu. Zuerst manchmal benötigen Sie eine dynamic_cast (Mehrfachvererbung ...) aber sind sicher, dass es nicht fehlschlagen kann (z. B. virtuelle Funktion gibt den Typ zurück). Oder 2. Sie behaupten, dass es gültig ist, aber behalten Sie die dynamische_Sendung, so dass Sie eine Null-Zeiger-Ausnahme im Freigabemodus anstelle von UD erhalten. Dies ist IMO besser als eine zusätzliche Überprüfung und werfen für den Fall (sollte sein) unmöglich. (z. B. eine Funktion gab den Typ zurück, daher sollte sie umsetzbar sein, aber Sie möchten sich vor einem C & P-Fehler in dieser Funktion schützen. Tricky-Makros: Deshalb habe ich eine Konfigurationsoption vorgeschlagen – Flamefire

Verwandte Themen