(artykuł)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
dział serwisuArtykuły
kategoriaInne artykuły
artykułBezpłatne narzędzia ułatwiające wykrywanie błędów w programach C/C++
Autor: Grzegorz 'baziorek' Bazior

Bezpłatne narzędzia ułatwiające wykrywanie błędów w programach C/C++

[artykuł]

Motywacja do artykułu

Doświadczonych programistów nie trzeba przekonywać, że pisząc programy zdarzają się błędy, wg badań i książek średnio na 1000 linii kodu występuje 1-25 defektów (niekoniecznie błędów), a ilość błędów nie jest bardzo zależna od języka programowania. Mimo iż w wielu firmach istnieje obowiązkowy proces review, to jednak najtaniej wykryć maksymalnie dużo błędów automatycznie i właśnie w niniejszym artykule zaprezentuje parę bezpłatnych narzędzi do wykrywania błędów. Nie twierdzę, że zaprezentowane tutaj narzędzia są najlepsze w tym co robią (ani, że same nie zawierają błędów). Zapewne istnieje wiele narzędzi lepszych, nie tylko komercyjnych, ale też bezpłatnych.
Opisane tutaj narzędzia są głównie na system Linux, ale wiele z nich może działać również na innych systemach. Zasadniczo skupię się na narzędziach sprawdzających błędy na etapie kompilacji, dokonujących statycznej analizy, a także działających na etapie wykonywania, z zaprezentowanych narzędzi są to m.in:
  • ustawienia kompilatorów: gcc, clang
  • narzędzia do statycznej analizy: cppcheck, clang-tidy, CLazy, scan-build, flawfinder, include-what-you-use, Copy-Paste-Detector, MOSS, Splint, Lizard, SourceCode Monitor, clang-format, KWStyle, Sonar i inne
  • narzędzia do dynamicznej analizy: Valgrind, debugger i inne

Zanim zdamy się na narzędzia

Powinienem zacząć od paru rzeczy, o których wszyscy wiedzą, w rodzaju "staraj się nie robić błędów", ale to wie każdy (i każdy się stara, a wychodzi jak zwykle). W tym paragrafie musiałbym streścić parę książek, więc pominę rzeczy banalne i zacznę od innej rzeczy. Język C++ się zmienia, pojawiają się różne udogodnienia dla programistów wraz z kolejnymi standardami. Warto je znać i używać ich zanim zaczniemy korzystać z innych narzędzi. Składnia języka mówi nam bardzo wiele, jest zrozumiała dla innych programistów (lub przynajmniej powinna być) i nie wymaga żadnego narzutu czasowego (zwykła kompilacja). Nie będę wymieniał tych mechanizmów, gdyż wymagałoby to wymienienia dużej części nowych mechanizmów języka programowania, dla ciekawych podrzucam linki do nowości w C++11 (Wikipedia), C++14 (Wikipedia) oraz C++17 (Wikipedia). Poza mechanizmami języka warto znać bibliotekę standardową i algorytmy uogólnione - algorytmy standardowe są powszechnie znane, czytelne oraz co najważniejsze - zrobione dobrze.
To wszystko jest w standardzie, więc każdy kompilator, zgodny z danym standardem, je obsługuje niezależnie od systemu operacyjnego.
Zanim weźmiemy się za używanie dodatkowych narzędzi do wykrywania błędów wpierw musimy dobrze znać udogodnienia z aktualnego (lub aktualnie używanego w naszej firmie) standardu C++.

Jedynie nieomylny - kompilator

Kompilator, poza zamianą kodu rozumianego przez nas na rozumiany przez komputer, ma bardzo wiele funkcji. Wśród nich jest wiele konfigurowalnych funkcji wykrywających błędy, które są jednak domyślnie wyłączone. Wykrywanie błędów przy pomocy kompilatora jest:
  • szybkie - wszak i tak kod kompilujemy
  • nieomylne - jak coś kompilator uzna, za błąd/ostrzeżenie to nie możemy powiedzieć, żeby się pomylił.
Niestety kompilatory nie dostarczają mechanizmów do sprawdzania wszystkich potencjalnych błędów, dlatego zasadne może być używanie innych narzędzi.
Aby przy pomocy kompilatora znajdywać maksymalnie dużo błędów warto używać wielu kompilatorów na różnych systemach.

Kompilatory mają wiele funkcji pomagających w automatycznym wykrywaniu błędów, jednakże w niniejszym artykule skupię się na argumentach uruchomienia.

Kompilator gcc

Jest to wciąż jeszcze najpopularniejszy kompilator na systemach uniksowych, ma on wiele flag do sprawdzanie błędów/ostrzeżeń kompilatora. Najpopularniejsze z nich to
-Wall -Wextra -pedantic
, ale jest ich znacznie więcej. W dokumentacji ostrzeżeń kompilatora gcc na dzień pisania artykułu znalazłem ponad 200 flag ostrzeżeń. Ta sama flaga
-Wall
 zawiera w sobie inne składowe, które można włączyć indywidualnie, deaktywować, uznać za błąd (listę flag zacytowano z ww. strony (z pominięciem flag dla nie C/C++)):
-Waddress  
-Warray-bounds=1 (only with -O2) 
-Wbool-compare 
-Wbool-operation 
-Wc++11-compat  -Wc++14-compat 
-Wcatch-value (C++ and Objective-C++ only) 
-Wchar-subscripts 
-Wcomment 
-Wduplicate-decl-specifier (C and Objective-C only)
-Wenum-compare (in C/ObjC; this is on by default in C++)
-Wenum-conversion in C/ObjC;
-Wformat  
-Wint-in-bool-context 
-Wimplicit (C and Objective-C only)
-Wimplicit-int (C and Objective-C only)
-Wimplicit-function-declaration (C and Objective-C only)
-Winit-self (only for C++)
-Wlogical-not-parentheses
-Wmain (only for C/ObjC and unless -ffreestanding) 
-Wmaybe-uninitialized
-Wmemset-elt-size
-Wmemset-transposed-args
-Wmisleading-indentation (only for C/C++)
-Wmissing-attributes
-Wmissing-braces (only for C/ObjC)
-Wmultistatement-macros 
-Wnarrowing (only for C++) 
-Wnonnull 
-Wnonnull-compare 
-Wopenmp-simd
-Wparentheses 
-Wpessimizing-move (only for C++) 
-Wpointer-sign 
-Wreorder  
-Wrestrict  
-Wreturn-type 
-Wsequence-point 
-Wsign-compare (only in C++) 
-Wsizeof-pointer-div
-Wsizeof-pointer-memaccess
-Wstrict-aliasing 
-Wstrict-overflow=1 
-Wswitch 
-Wtautological-compare 
-Wtrigraphs 
-Wuninitialized 
-Wunknown-pragmas 
-Wunused-function 
-Wunused-label    
-Wunused-value    
-Wunused-variable 
-Wvolatile-register-var
Z kolei
-Wextra
 włącza następujące flagi:
-Wclobbered 
-Wcast-function-type 
-Wdeprecated-copy (C++ only)
-Wempty-body
-Wignored-qualifiers
-Wimplicit-fallthrough=3
-Wmissing-field-initializers
-Wmissing-parameter-type (C only)
-Wold-style-declaration (C only)
-Woverride-init
-Wsign-compare (C only)
-Wredundant-move (only for C++)
-Wtype-limits
-Wuninitialized
-Wshift-negative-value (in C++03 and in C99 and newer) 
-Wunused-parameter (only with -Wunused or -Wall)
-Wunused-but-set-parameter (only with -Wunused or -Wall)
Oczywiście jeśli podczas kompilacji będą się pojawiać tylko warningi to po jakimś czasie będzie ich bardzo wiele, a wielu programistów nie będzie się nimi przejmować. Dlatego chcąc wymusić przejmowanie ostrzeżeniami kompilatora należy zamienić je w błędy przy pomocy -Werror lub -pedantic-error (zamiast flagi -pedantic). Z drugiej jednak strony jeśli włączymy wszystkie możliwe flagi to programiści mogą mieć większe chęci na opuszczenie danej firmy.

Jest wiele flag, ale które użyć? Czy testować każdą po kolei?
Można, ale informatyk zanim coś zrobi sprawdza, czy nie zrobili tego już inni. Zrobili i to jeszcze nie byle kto, bo firma RedHat (znana z komercyjnej dystrybucji linuxa oraz z bezpłatnej dystrybucji Fedora). RedHat w artykule wymieniła flagi jakie stosuje wraz z uzasadnieniem, z tej to strony cytuję następujące flagi:
-D_FORTIFY_SOURCE=2
-D_GLIBCXX_ASSERTIONS
-fasynchronous-unwind-tables
-fexceptions
-fpie -Wl,-pie
-fpic -shared
-fplugin=annobin
-fstack-clash-protection
-fstack-protector or -fstack-protector-all
-fstack-protector-strong
-g
-grecord-gcc-switches
-mcet -fcf-protection
-O2
-pipe
-Wall
-Werror=format-security
-Werror=implicit-function-declaration
-Wl,-z,defs
-Wl,-z,now
-Wl,-z,relro

Ignorowanie wyświetlania ostrzeżeń z argumentów uruchomienia kompilatora

Gdy w jednym fragmencie kodu nie chcemy mieć sygnalizowanego błędu (bo robimy błąd celowo), nie musimy wyłączać danej opcji kompilatora dla całego pliku, wystarczy, że tylko wyłączymy ją przed newralgiczną linijką:
C/C++
#include <iostream>

int main()
{
    int a;
   
    #pragma GCC diagnostic push // zapisanie aktualnego stanu
    #pragma GCC diagnostic ignored "-Wuninitialized"
    if( a )
         std::cout << "a != 0!\n";
   
    #pragma GCC diagnostic pop // przywrocenie poprzedniego stanu
    if( a )
         std::cout << "a != 0!\n";
   
}
Dzięki temu używając flagi uruchomieniowej
-Wuninitialized
 pośrednio lub bezpośrednio, ostrzeżenie z powodu niezalinicjalizowanej zmiennej zobaczymy dopiero w drugim ifie.

Przykładowa komenda kompilacji dla języka C

gcc --std=c11 -Wall -Wextra -Wpedantic -Winit-self -Wchkp -Wdouble-promotion -Wformat-overflow=2 -Wformat=2 -Wformat-security -Wformat-signedness -Wformat-truncation=2 -Wformat-y2k -Wnull-dereference -fdelete-null-pointer-checks -Wmissing-include-dirs -Wshift-overflow=2 -Wswitch-default -Wswitch-enum -Wsync-nand -Wunused -Wunused-const-variable -Wstringop-overflow=4 -Wsuggest-attribute=const -Wsuggest-final-types -Wsuggest-final-methods -Walloc-zero -Wduplicated-branches -Wduplicated-cond -Wtrampolines -Wfloat-equal -Wtraditional-conversion -Wshadow -Wshadow=local -Wshadow=compatible-local -Wundef -Wunused-macros -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion -Wdangling-else -Wjump-misses-init -Wfloat-conversion -Wlogical-op -Wstrict-prototypes -Wold-style-definition -Wnormalized=nfkc -Wpacked -Wredundant-decls -Winline -Winvalid-pch -Wvector-operation-performance -Wvla -Woverlength-strings -Wunsuffixed-float-constants -Wstack-protector -Weffc++ file.c -c -o file.o

Używamy starego kompilatora gcc w projekcie

Zdarza się, że komponenty, których używamy są skompilowane przy pomocy starszej wersji kompilatora, a co za tym idzie cały projekt musimy budować starszą wersją kompilatora. Rozwiązanie wtedy może być następujące - budować projekt starszym kompilatorem i tak musimy, ale możemy oprócz tego budować nowym, zgodnym z nowszymi standardami kodu z użyciem bardziej zaawansowanych flag kompilacji. Rozwiązanie wydaje się dziwne, ale widziałem firmę, która takie stosuje. Rozwiązanie takie ma jeszcze taką zaletę, że przeskakując na nowszy kompilator przeskok będzie mniej czasochłonny.

Clang

Jest to coraz popularniejszy kompilator. Ma on również sporą ilość funkcji, do wykrywania potencjalnych błędów/nieprawidłowości w kodzie programu. Szczegóły na temat tych flag można przeczytać w dokumentacji clanga. Dla uspokojenia dodam, że Clang dla zgodności z gcc obsługuje dużą część jego flag, chociaż niektóre z nich "są dla zgodności z gcc i nie mają wpływu na clanga". Flag do obsługi ostrzeżeń kompilatora naliczyłem prawie 800.

Narzędzia do statycznej analizy kodu

Są to narzędzia omylne, które dokonują parsowania kodu, ale nie są kompilatorem. Niejednokrotnie zdarza się, że za błąd/ostrzeżenie uznają coś, co błędem nie jest. Jest to rozwiązanie gorsze niż kompilacja, niemniej jednak jak kompilator nie dostarcza pewnej funkcjonalności potrzebujemy używać statycznej analizy.

Cppcheck

Jest to jedno z najpopularniejszych narzędzi do statycznej analizy kodu programów napisanych w C++, można go pobrać ze strony domowej lub zainstalować z repozytorium. Poza tym wiele naszych środowisk programistycznych posiada różne wtyczki do użycia cppckecka (eclipse, code:blocks, jendkins, nawet Tortoise SVN i inne).
Zasadniczo, żeby wyświetlić co jest sprawdzane wystarczy wpisać:
cppcheck --doc
,a bardziej czytelny opis znajduje się na stronie dokumentacji.
Z założenia narzędzie ma nie uznawać za błąd fałszywych alarmów. Jednak można włączyć większą dokładność, co się wiąże z większą ilością fałszywych alarmów (to nie jest kompilator), ale i tak nie znajdzie ono wszystkich błędów. Działanie CppChecka polega na sprawdzeniu wszystkich ścieżek wykonywania (nie rozstrzyga, czy dany warunek jest zawsze prawdziwy/fałszywy). Poza samym sprawdzaniem kodu umożliwia też sprawdzanie stylu kodu, a nawet dostarcza biblioteki do zarządzania kodem (pobierania nazw funkcji i czy jest statyczna itp). Osoby, które nie lubią wiersza poleceń mogą również użyć cppcheck-gui i wyklikać dostępne funkcje.

Uruchamianie narzędzia

Przykładowo aby uruchomić narzędzie piszemy:
cppcheck plik.cc
lub jeśli chcemy katalog sprawdzić rekurencyjnie piszemy
cppcheck plik/sciezka/do/projektu
Oczywiście mamy dostępne wiele przydatnych opcji:
  • -iplik
     lub
    -ikatalog
     - wykluczenie sprawdzania danego katalogu
  • --enable=warning,performance,unusedFunction,style,information,portability
     - umożliwia włączenie dodatkowych sprawdzeń, które mogą już raportować fałszywe alarmy
  • --project=...
     - umożliwia wczytanie ustawień dla projektu z rozszerzeniem .cppcheck lub .sln, a nawet .json (wygenerowany przez
    cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .
    ), czy .bpr (C++ Builder)
  • -DXXX
     - dodanie stałej preprocesora XXX, domyślnie jednak cppcheck będzie sprawdzał wszystkie konfiguracje stałych preprocesora (można ustawić limit --max-configs)
  • -UXXX
     -jw. ale jakby
    #undef
  • -Isciezka/do/naglowkow
     - oczywiscie nie jest zalecane podawanie wszystkich sciezek, jedynie tych co mają stałe preprocesora
  • -template=gcc
     - wyświetla błędy w formacie jak np. kompilator gcc, są też inne kompilatory np. vs, można też własnoręcznie formatować wyjście
  • --suppress=memleak:src/file1.cpp
     - ignorowanie pewnego błędu w danym pliku, nazwa pliku jest opcjonalna
  • --suppressions-list=suppressions.txt
     -plik z wyjątkami, w formacie np. exceptNew:src/file1.cpp
  • --xml file.xml
     - wydruk do pliku XML
  • --addon=misc.py
     - uruchomienie dodatku o nazwie misc.py (dołączonego do cppchecka)
  • --library=*.cfg
     - jest możliwe dodanie własnych sprawdzeń bibliotek, np. windows.cfg, wxwidgets.cfg, qt.cfg, std.cfg, posix.cfg i inne, można też zdefiniować własny plik wg instrukcji
  • --addon=ścieżkaDoDodatku
     - można też używać własnych dodatków napisanych w pythonie, w paczce z Cppcheckiem mamy kilka przykładowych dodatków.

Ignorowanie raportowania błędów przez cppcheck

Kluczowa funkcjonalność - możliwość ignorowania sprawdzenia danego błędu dla pewnej instrukcji, aby to osiągnąć należy poprzedzić ją przez:
C/C++
// cppcheck-suppress arrayIndexOutOfBounds
Można też z komentarzem:
C/C++
// cppcheck-suppress arrayIndexOutOfBounds // some comment

Cppcheck-gui

W paczce z Cppcheckiem jest też dostarczone oficjalne GUI, napisane przy użyciu QT, domyślnie opcja instalacji GUI jest wyłączona, ale można ją włączyć. Przykładowy wygląd GUI:

Bibliografia cppcheck

Oficjalna dokumentacja
Artykuły opisujące działanie oraz tworzenie reguł do sprawdzania stylu programowania

Przykładowa komenda

cppcheck -DONLINE_JUDGE --force --enable=warning,performance,unusedFunction,style,information --template=gcc --inline-suppr --library=gnu.cfg --library=gtk.cfg --library=std.cfg --library=cppcheck-lib.cfg --library=motif.cfg --library=qt.cfg --library=windows.cfg --library=sdl.cfg --library=gnu.cfg --library=wxwidgets.cfg --library=sfml.cfg --library=posix.cfg --library=embedded_sql.cfg --library=avr.cfg --suppress=missingIncludeSystem * > /dev/null

Clang-tidy

Jest to narzędzie do statycznej analizy, do znajdywania błędów programistycznych. Poza tym jest to biblioteka do własnoręcznego zarządzania kodem m.in. w celu pisania własnych narzędzie do sprawdzania. Jako ciekawostkę dodam, że Qt Creator dokonuje sprawdzenia kodu przy pomocy Clang-tidy w ramach środowiska. Z mojego doświadczenia to bardzo dobrze działa (czyli wykrywa błędy szybko, nie myląc się, chociaż czasami spowalnia).

Aby zobaczyć jakie funkcje sprawdzania są dostępne należy wpisać:
clang-tidy -list-checks
 - wyświetli to wtedy domyślne checki
clang-tidy -list-checks -checks=*
 - wyświetli wszystkie możliwe.
Przykładowo aby włączać/wyłączać checki można napisać np.:
clang-tidy plik.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus*
 - komenda wyłączy domyślne checki, a włączy checki
clang-analyzer-*
 za wyjątkiem
-clang-analyzer-cplusplus*
.
Bardzo cenną funkcjonalnością clang-tidy jest możliwość naprawiania ostrzeżeń/błędów, w tym celu należy dodac argument
-fix
 lub
-fix-errors
, oczywiście pod warunkiem, że narzędzie wie jak naprawić błąd. Przykładowo jeśli chcemy zamienić using namespace std na każdorazowe kwantyfikowanie przez std:: to właśnie ta opcja zrobi to za nas automatycznie.

Clang-tidy ma też możliwość używania takich samych flag jakich używamy do budowania projektu, flagi te można wygenerować z CMake'a:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ścieżka/do/projektu
, wtedy powstanie nam plik compile_commands.json, który zawiera informacje przy pomocy których komend budujemy poszczególne pliki. Ten plik jest używany przez clang-tidy. Oczywiście jeśli nie chcemy tworzyć tego pliku możemu podać argumenty kompilacji od razu poprzedzając je dwoma minusami np.:
clang-tidy tmp.cc -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus* -fix-errors -- -Wall -std=c++17

Deaktywacja ostrzeżeń clang-tidy

Aby wyłączyć ostrzeżenia należy obok podejrzanej linijki wpisać w komentarzu
C/C++
// NOLINT
np.:
C/C++
int a;
if( a ) { } // NOLINT
Oczywiście możemy też podać powód:
C/C++
int a;
if( a ) { } // NOLINT: Because of creating article
Możemy też wyłączyć tylko konkretne ostrzeżenie:
C/C++
if( a ) { } // NOLINT(clang-analyzer-core.uninitialized.Branch)
W przypadku dłuższych linijek możemy wyłączyć ostrzeżenia dla następnej linijki:
C/C++
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
if( a ) { }

Bibliografia clang-tidy

Dokumentacja clang-tidy
lista oficjalnych sprawdzeń wraz z przykładami i opisem.

CLazy

Jest to narzędzie napisane przez programistów utrzymujących Qt, używa się go analogicznie jak clang-tidy. Szczegółowo nie będę go opisywał, gdyż jest niszowe. Jeśli ktoś używa bibliotek QT może się pokusić o sprawdzanie częstych błędów w używaniu biblioteki QT. Jej twórcy napisali narzędzie CLazy, którego używa też Qt Creator. Clazy używa się zamiast kompilatora np. dla cmake'a
cmake . -DCMAKE_CXX_COMPILER=clazy
, można też używać go analogicznie jak clang-tidy, mając wygenerowane komendy do pliku compile_commands.json, wtedy używamy binarki:
clazy-standalone
.

Scan-build

Jest to kolejne narzędzie, które wykonuje statyczną analizę kodu. Nie trzyma ono informacji jak budowany jest projekt. Zamiast tego zasłania komendę budującą, do której przesyła normalnie podane argumenty, jednakże wcześniej dokonuje analizy. Z interesujących właściwości jest sposób uruchamiania z prawie dowolną komendą budującą np.:
scan-build make
scan-build make -j6
scan-build g++ -c file1.cc file2.cc
scan-build xcodebuild
scan-build ./configure
Scan-build generuje raport z analizy w postaci wielu plików HTML (na każdy błąd oddzielny plik), raporty te można przeglądać w bardzo przyjemnej formie:
Oczywiście ścieżka wygenerowania raportów jest podawana na końcu wykonawania analizy, ale można też podać ścieżkę do generowania plików HTML.

Przykładowe argumenty uruchomienia

  • -analyze-headers
     - włączenie również analizy włączanych plików
  • --force-analyze-debug-code
     - włączenie analizy kodu debugowego (nawet jeśli jest deaktywowany dla pewnych ścieżek kompilacji)
  • --status-bugs
     - domyślnie zwrócony status to status komendy budującej, ale jeśli użyjemy tego argumentu to zostanie zwrócona wartość niezerowa w razie znalezienia potencjalnego błędu
  • -stats
     - włączenie generowania statystyk projektu
  • -enable-checker [nazwa]
     - włączenie dodatkowego sprawdzania
  • -disable-checker [nazwa]
     - wyłączenie konkretnego sprawdzania
  • --use-c++=[kompilator]
     - wymuszenie użycia konkretnego kompilatora dla c++
  • --use-c=[kompilator]
     - wymuszenie użycia konkretnego kompilatora dla c

Możemy też sprawdzić, jakie opcje sprawdzania są włączone domyślnie (wraz z opisem), poniżej komenda i przykładowy wydruk dla opcji domyślnie włączonych (zaczynają się od +):
$ scan-build --help-checkers-verbose | grep -P '^[[:space:]]\+'
 + apiModeling.google.GTest        Model gtest assertion APIs
 + core.CallAndMessage             Check for logical errors for function calls and Objective-C message expressions (e.g., uninitialized arguments, null function pointers)
 + core.DivideZero                 Check for division by zero
 + core.DynamicTypePropagation     Generate dynamic type information
 + core.NonNullParamChecker        Check for null pointers passed as arguments to a function whose arguments are references or marked with the 'nonnull' attribute
 + core.NonnilStringConstants      Assume that const string-like globals are non-null
 + core.NullDereference            Check for dereferences of null pointers
 + core.StackAddressEscape         Check that addresses to stack memory do not escape the function
 + core.UndefinedBinaryOperatorResult
 + core.VLASize                    Check for declarations of VLA of undefined or zero size
 + core.builtin.BuiltinFunctions   Evaluate compiler builtin functions (e.g., alloca())
 + core.builtin.NoReturnFunctions  Evaluate "panic" functions that are known to not return to the caller
 + core.uninitialized.ArraySubscript
 + core.uninitialized.Assign       Check for assigning uninitialized values
 + core.uninitialized.Branch       Check for uninitialized values used as branch conditions
 + core.uninitialized.CapturedBlockVariable
 + core.uninitialized.UndefReturn  Check for uninitialized values being returned to the caller
 + cplusplus.NewDelete             Check for double-free and use-after-free problems. Traces memory managed by new/delete.
 + cplusplus.NewDeleteLeaks        Check for memory leaks. Traces memory managed by new/delete.
 + cplusplus.SelfAssignment        Checks C++ copy and move assignment operators for self assignment
 + deadcode.DeadStores             Check for values stored to variables that are never read afterwards
 + nullability.NullPassedToNonnull
 + nullability.NullReturnedFromNonnull
 + security.insecureAPI.UncheckedReturn
 + security.insecureAPI.getpw      Warn on uses of the 'getpw' function
 + security.insecureAPI.gets       Warn on uses of the 'gets' function
 + security.insecureAPI.mkstemp    Warn when 'mkstemp' is passed fewer than 6 X's in the format string
 + security.insecureAPI.mktemp     Warn on uses of the 'mktemp' function
 + security.insecureAPI.vfork      Warn on uses of the 'vfork' function
 + unix.API                        Check calls to various UNIX/Posix functions
 + unix.Malloc                     Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().
 + unix.MallocSizeof               Check for dubious malloc arguments involving sizeof
 + unix.MismatchedDeallocator      Check for mismatched deallocators.
 + unix.StdCLibraryFunctions       Improve modeling of the C standard library functions
 + unix.Vfork                      Check for proper usage of vfork
 + unix.cstring.BadSizeArg         Check the size argument passed into C string functions for common erroneous patterns
 + unix.cstring.NullArg            Check for null pointers being passed as arguments to C string functions
Oprócz domyślnie włączonych, ponad 30 opcji, naliczyłem jeszcze ponad 90 opcji do włączenia.

Przykładowa komenda

scan-build -o myReports --force-analyze-debug-code -no-failure-reports --show-description -enable-checker alpha.clone.CloneChecker -enable-checker alpha.core.BoolAssignment g++ file.cc -o file

Bibliografiak scan-build

Dokumentacja scan-build

Flawfinder

Jest to ciekawe narzędzie, które sprawdza kod pod względem potencjalnie niebezpiecznych instrukcji. Pomysł dobry, a cenną zaletą tego narzędzia jest podawanie identyfikatora zagrożenia zgodnego z bazą Common Weakness Enumeration. Baza ta zawiera dokładne przykłady kodu, wraz z wyczerpującym opisem. Flawfinder został napisany przez specjalistę od oprogramowania -Dawida Wheelera, który jest autorem wielu artykułów o bezpieczeństwie, prowadzi szkolenia, nawet można przeczytać książkę, dostępną na jego stronie. Ponadto jest autorem wielu narzędzi, w tym opisywanego.
Flawfinder jest z założenia prostym narzędziem, które zawiera w sobie bazę częstych problemów związanych z bezpieczeństwem i po prostu skanuje kod w poszukiwaniu tych problemów i je raportuje, ignorując komentarze (z wyjątkiem komentarzy dedykowanych dla narzędzia). Nie dokonując analizy przepływu, w zasadzie działa prawie jak zaawansowany grep. Z interesujących faktów jest to, że narzędzia tego używało wiele firm i znalazło ono parę błędów w ich kodzie, które to błędy zostały zignorowane, powodując poważniejsze błędy. Wiemy to, gdyż firmy te się pokornie przyznały (szczegóły opisano na stronie opisującej flawfinder).

Argumenty uruchomienia (przykładowe)

  • −−listrules
     - wyświetlenie aktualne reguły i ich poziom ryzyka
  • −−patch=plikZeZmianami
     - analizuje tylko kod z danego patha
  • −−minlevel=X
     - ustawia minimalny poziom ryzyka, w skali 0(brak)-5 (maksymalne ryzyko), domyślnie 1. Każdej znajdywanej rzeczy przysługuje poziom ryzyka, poniżej danego poziomu nie są one raportowane
  • −−falsepositive
     - ignoruj wykryte ostrzeżenia, jeśli najprawdopodobniej są fałszywymi alarmami
  • −−regexp=PATTERN
     - raportowanie tylko zagrożeń pasujących do wzorca, np. CWE-120
  • −−html
     - wyjście jako HTML zamiast zwykłego tekstu
  • −−csv
     - wyjście jako CSV
  • --singleline
     - wyświetlanie dla jednego ostrzeżenia jednej linii

Celowe deaktywowanie raportowania błędów

Autor narzędzia radzi nienaprawianie błędu, jeśli dobrze nie rozumiemy co w nim jest. Jako przykład podaje "nadgorliwą" naprawę znalezionego błędu w RedHatcie. Jeśli jednak mimo wszystko chcemy ignorować daną linię obok niej powinniśmy zawrzeć odpowiedni komentarz:
C/C++
system( "ls" ); // Flawfinder: ignore

Przykładowe uruchamianie

flawfinder file.cc
flawfinder --quiet --dataonly --nolink --columns --context --singleline --neverignore file.cc

Bibliografia Flawfinder'a

Strona domowa
Oficjalna dokumentacja
Książka on-line o zagrożeniach w kodzie ww. autora
Strona z najczęstszymi zagrożeniami bezpieczeństwa w kodzie Common Weakness Enumeration

Narzędzie do znajdywania niepotrzebnych includów - include-what-you-use

Narzędzie wydaje się mało znaczące, chociaż przy większych i starszych projektach ilość niepotrzebnych nagłówków puchnie, a to z kolei zwalnia szybkość kompilacji. Narzędzie działa w taki sposób, że dla każdego symbolu w pliku (typu, funkcji, makra, zmiennej) wyszukuje includy, które trzeba dodać, jak i te, które nie są konieczne. Robi to przy pomocy kompilatora clanga, a co za tym idzie raczej się nie myli.
Oczywiście pewne symbole są w pewnych nagłówkach, które rzadko się załącza bezpośrednio np.
std::unique_ptr < T >
 w kompilatorze gcc jest zdefiniowany w
#include <bits/unique_ptr.h>
, w takich sytuacjach można zmapować dla pewnych symboli nagłówki prywatne i publiczne (jak to zrobić).
Ma też funkcjonalność do dodawania komentarzy obok włączanych nagłówków, aby programista wiedział z jakich symboli dla danego nagłówka programista korzysta.

Uruchamianie

To narzędzie również wywołuje pod spodem kompilator, więc możemy używać go zamiast kompilatora:
include-what-you-use file.c -c -o file.o
make -k CXX=/path/to/include-what-you-use
CC="clang" CXX="clang++" cmake -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="/path/to/include-what-you-use;args" ...
Można też użyć komend kompilacji z pliku compile_commands.json, wygenerowanego przez cmake'a, do tego celu mamy dostarczone wraz z include-what-you-use skrypt
iwyu_tool.py
:
CC="clang" CXX="clang++" cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...
iwyu_tool.py -p .

Automatyczna naprawa

Cenną funkcjonalnością jest możliwość naprawiania includów dzięki dostarczonemu skryptowi
fix_includes.py
:
make -k CXX=/path/to/include-what-you-use 2> /tmp/iwyu.out
python fix_includes.py < /tmp/iwyu.out

Wyjątki od automatycznego modyfikowania includów

C/C++
#include <vector> // IWYU pragma: keep
class ForwardDeclaration; // IWYU pragma: keep
// IWYU pragma: private, include "public.h"
// IWYU pragma: no_include "private.h"
// IWYU pragma: no_forward_declare Public
Trzeci z powyższych mówi, żeby w nie sugerować "prywatnego" nagłówka, a "publiczny" jest podany obok. Dla naszej wygody dla wielu bibliotek nie musimy samodzielnie definiować prywatnych/publicznych nagłówków, gdyż wraz z include-what-you-use są dostarczone pliki konfiguracyjne do m.in. boost, stl, qt4, qt5, poco, winapi i inne. Aby z nich skorzystać piszemy (plik z rozszerzeniem *.imp musi być w ścieżce względnej lub bezwzględnej) np.:
include-what-you-use -Xiwyu --mapping_file=foo.imp file.cc

Kwestia includów, gdy używamy gcc

W tej sytuacji należy wyeksportować ścieżki do nagłówków z gcc do include-what-you-use, które używa clanga:
include-what-you-use $(echo | gcc -Wp,-v -x c++ - -fsyntax-only 2>&1 | grep  '^ /' | xargs -i echo "-isystem {}") file1.cc file2.cc

Bibliografia include-what-you-use.org

Strona domowa
pliki dokumentacji

Copy-paste-detector (CPD)

Jest to narzędzie do wykrywania duplikatów w liniach kodu, dostarczone wraz z zestawem narzędzi do statycznej analizy kodu https://pmd.github.io. Mimo iż samo PMD ma bardzo wiele funkcjonalności przy statycznej analizie i mimo iż wspiera wiele języków nie ma jeszcze wsparcia dla C i C++ (wspierane języki na moment pisania artykułu: Java, JavaScript, Salesforce.com Apex i Visualforce, PLSQL, Apache Velocity, XML, XSL). Co ciekawe dostarczony, wraz z PMD, CPD (copy-paste detector) wspiera C, C++, a także języki Java, C#, Groovy, PHP, Ruby, Fortran, JavaScript, PLSQL, Apache Velocity, Scala, Objective C, Matlab, Python, Go, Swift i Salesforce.com Apex i Visualforce. Ponadto narzędzie można używać zarówno z poziomu wiersza poleceń, jak i z dostarczonego GUI, a także są napisane pewne wtyczki do Anta czy Mavena. Narzędzie CPD działa dość prosto - rozbija kod na tokeny, które ze sobą porównuje celem znalezienia duplikatu. Niby proste, ale dzięki temu nawet jeśli pozmieniamy nazwy zmiennych to i tak pewne fragmenty kodu mogą być uznane za takie, na których użyto wzorca projektowego Copiego-Pastea.

Przykładowe użycie narzędzia

Po pobraniu paczki ze strony domowej PMD i rozpakowaniu wchodzimy do katalogu: {katalog/Z/PMD}/bin i tam wywułujemy:
./run.sh cpd --minimum-tokens 50 --files katalog/projektu
W odpowiedzi pojawi się wydruk informujący, ile linii (tokenów) się duplikuje, w których plikach zaczynając której linii oraz zostanie zacytowany ten kod. Wylistowane zostaną jednakże wyłącznie fragmenty zawierające ciągiem co najmniej 50 takich samych tokenów. Mimo wszystko taka forma wydruku jest niezbyt wygodna (ciężko ją parsować i przeglądać), dlatego można ustawić eksport raportu w formie XMLa.

Opis przykładowych parametrów

  • --minimum-tokens
     - parametr obowiązkowy, przy ilu zduplikowanych tokenach zwracać uwagę
  • --files
     - parametr obowiązkowy, podajemy tutaj katalog lub pliki do przeszukania
  • --language
     - język programowania dla plików, domyślnie jest to java, więc powinniśmy napisać cpp
  • --skip-duplicate-files
     - włączenie pomijania plików o takiej samej nazwie i długości
  • --exclude
     - wykluczanie
  • --non-recursive
     - wyłączenie przeglądania rekurencyjnego
  • --format
     - format raportu, domyślnie tekst, ale może być XML
  • --ignore-literals
     - włączenie ignorowania wartości liczbowych i tekstowych
  • --ignore-identifiers
     - włączenie ignorowania nazw zmiennych i stałych
  • --help
     - wyświetlenie pomocy

Uruchamianie GUI

Aby uruchomić GUI musimy z katalogu jw. uruchomić:
Linux:
./run.sh cpdgui
Windows:
cpdgui.bat
Przykładowe okno możemy zobaczyć tutaj:

Przykładowa komenda

./run.sh cpd --language cpp --minimum-tokens 50 --ignore-annotations --ignore-identifiers --ignore-literals --files katalog/do/plikow

Bibliografia CPD

Podstrona PMD poświęcona CPD

Decykowane narzędzie dla nauczycieli akademickich do wykrywania plagiatów w kodzie studentów

Ponoć najlepszym do tego narzędziem jest MOSS, napisany i utrzymywany przez uniwersystet w Standordzie, można na niego otrzymać bezpłatną licencję wysyłając maila zwykłym tekstem do automatu:
moss@moss.stanford.edu o treści:
registeruser
mail username@domain
Więcej informacji na podstronie uniwersytetu.

Splint - popularne narzędzie do analizy programów w C

Jest to bardzo popularny program do statycznej analizy programów napisanych w języku C. Narzędzie jest napisane i rozwijane przez University of Virginia od bardzo wielu lat, przez co ma bardzo dużo funkcji, które na szczęście są obszernie opisane.
Ma on wyszukiwać problemów z bezpieczeństwem oraz błędy w kodzie. Narzędzie to stanowi kontynuację wielu znanych projektów (m.in. LINT). Analiza kodu przy pomocy Splinta może być polepszona dzięki stosowaniu specjalnych adnotacji w komentarzach.

Przykładowe użycie

W zasadzie nazwa programu i listujemy pliki
splint *.c
Przykładowy wydruk (zacytowany z [a href= "http://splint.org/manual/manual.pdf" name=""manuala"]):
sample.c: (in function faucet)
sample.c:11:12: Fresh storage x not released before return
  A memory leak has been detected. Storage allocated locally is not released  before the last reference to it is lost. (Use -mustfreefresh to inhibit  warning)
  sample.c:5:47: Fresh storage x allocated

Argumenty uruchomienia (przykładowe)

Wszystkich argumentów uruchamiania jest kilkadziesiąt i mają kilkadziesiąt stron w dokumentacji (niestety bez przykładów). Ogólnie mamy pewne kategorie argumentów uruchomienia: ogólne ustawienia (np. pliki konfiguracyjne, ściezki do nagłówków, do bibliotek itp.), ustawienia wydruku, czy wreszcie ustawienia poszczególnych rzeczy do sprawdzenia. Z najważniejszych argumentów mamy możliwość wyboru sposobu czułości wykrywania potencjalnych błędów (im większa czułość, tym też większa szansa na fałszywy alarm): weak, standard (domyślna), checks, strict; np.
splint -weak *.c
. Poza tym możemy ustawienia wrzucić do pliku i podać do niego ścieżkę jako argument:
splint -f {plikkonfiguracyjny} *.c
.

Sterowanie Splintem z poziomu kodu -"adnotacje"

Oczywiście Splint to nie jest kompilator, więc te "adnotacje" muszą wciąż być poprawne w języku C, dlatego są one w komentarzu i mają następującą formę:
/*@   @*/
 np.
/*@null@*/
 oznacza, że parametr funkcji może być NULL, a z kolei:
/*@+charint -modifies =showfunc@*/
 - modyfikuje konfigurację (np.argumenty uruchamiania) narzędzia. Tych "adnotacji" jest tak dużo, jakby wręcz to był inny język (tak jak Java SE i Java EE). Zapewne ta ilość wynika z długiego czasu życia narzędzia, które pamięta jeszcze, gdy w języku C funkcje miały jeszcze formę K&R.

Bibliografia

Strona domowa narzędzia Splint
Najnowsza dokumentacja narzędzia Splint
Inne dokumenty (w tym publikacje naukowe) poświęcone narzędziu Splint
Repozytorium narzędzia Splint

Lizard - narzędzie do mierzenia złożoności cyklomatycznej McCabe'a

Złożoność cyklomatyczna jest to metryka używana do pomiaru skomplikowania kodu, której podstawą jest ilośc punktów decyzyjnych programu. Im większa złożoność cyklomatyczna funkcji, tym funkcja bardziej skomplikowana, bardziej podatna na błędy, trudniejsza w testowaniu czy utrzymaniu. Oczywiście dużą złożonośc mają w sobie funkcje, które zawierają jednego switcha z dużą ilość opcji, mimo iż stosowanie tego typu switchy uchodzi za czytelne.

Lizard jest bezpłatnym narzędziem, a równocześnie biblioteką napisaną w pythonie do analizy m.in. złożoności cyklomatycznej, ale również:
  • linii kodu bez komentarzy
  • ilości toneków w funkcji
  • ilości parametrów w funkcji

Wspiera on nie tylko C/C++ (do wersji C++14), ale też: Java
C# (C Sharp), JavaScript, Objective-C, Swift, Python, Ruby, TTCN-3, PHP, Scala, GDScript, Golang, Lua.

Wybrane argumenty uruchomienia

  • -C CCN, --CCN CCN
     - Granica złożoności cyklomatycznej, powyżej której pojawi się ostrzeżenie. Wartość domyślna to 15.
  • -L LENGTH, --length LENGTH
     - Granica długoścu finkcji, po której przekroczeniu pojawi się ostrzeżenie. Wartość domyślna to 1000 toneków.
  • -a ARGUMENTS, --arguments ARGUMENTS
     - Limit ilości argumentów funkcji.
  • -w, --warnings_only
     - Pokazuje tylko ostrzeżenia (domyślnie pokazuje całe podsumowanie) w stylu clang/gcc.
  • -i NUMBER, --ignore_warnings NUMBER
     - Jeśli ilość ostrzeżeń przekroczy podaną wartość program zakończy analizę z kodem niezerowym (błąd).
  • -x EXCLUDE, --exclude EXCLUDE
     - Wykluczanie plików pasujących do wyrażenia regularnego.
  • -X, --xml
     - Wydruk w formie XMLa w stylu cppncss.
  • -t WORKING_THREADS, --working_threads WORKING_THREADS
     - Ilość wątków, domyślnie 1.
  • -m, --modified
     - Włączenie tej opcji modyfikuje domyślne liczenie złożoności cyklomatycznej dla switcha na zawsze jeden.
  • -E EXTENSIONS, --extension EXTENSIONS
                          Używanie ostrzeżeń, domyślnie dostarczone
                         
    -Ecpre
     -ignorowanie gałęzi-else,                      
    -Ewordcount
    : liczenie ilości wystąpień słów.
    -Eoutside
    : kod globalny jako jedna funkcja.
  • -W WHITELIST, --whitelist WHITELIST
                          Biała lista, domyślnie jest to plik.
                          './whitelizard.txt'.

Zawołanie jako biblioteki

W kodzie języka Python możemy zawołać:
import lizard
analisedFile = lizard.analyze_file("../cpputest/tests/AllTests.cpp")
print analisedFile.__dict__

Przykładowe zawołanie komendy - jako funkcja bashowa

lizard --working_threads 4 -Tcyclomatic_complexity=10 -Tlength=50 -Tnloc=40 -Tparameter_count=4 -w

Bibliografia

Strona domowa, kod źródłowy, instrukcja

SourceCode Monitor

Jest to kolejny program do zbierania matryk, napisany dla odmiany w C++. Ma on taką zaletę/wadę, że dostarcza GUI, więc można sobie wygodnie przejrzeć metryki dla kodu napisanego w wielu językach programowania (C++, C, C#, VB.NET, Java, Delphi, Visual Basic (VB6), HTML). Gui wygląda w taki sposób:
Można utworzyć projekt postępując wg instrukcji.
Narzędzie w przypadku C++ liczy:
  • Instrukcje: if, for, while. Gałęzie preprocesora inne niż #if (czyli #elif i #else) są ignorowane.
  • Procentowo linie z komentarzem
  • Klasy/struktury
  • Metody na klasę
  • Funkcje
  • Średnią ilość instrukcji na metodę
  • Maksymalną złożonośc funkcji
  • Średnią złożoność funkcji
  • Ilość zawołań funkcji
  • Maksymalną/średnią głębokość bloku
SourceCode Monitor w porównaniu do narzędzia Lizard zlicza więcej, ale ze względu na brak wiersza poleceń nadaje się tylko do ręcznego przeglądania i analizowania przez wygodne GUI.

Bibliografia

Strona domowa
Dokument help narzędzia, który co ciekawe jest znacznie obszerniejszy niż dane w internecie.

Sprawdzanie stylu

Jeśli wielu programistów pracuje nad jednym kodem, pożądanym jest, aby kod wszędzie wyglądał tak samo. W tym celu ujednolica się Coding Standard dla danego produktu w firmie. Do niedawna nie było ogólnego standardu programowania dla języka C++ (obecnie komitet standaryzacyjny udostępnia wytyczne). Z innych znanych stylów programowania mamy:
Styl projektu GNU
Styl Google
Styl Linusa Torvaldsa dla jądra Linux i inne style

Clang-format

Jest to narzędzie zrobione na bazie kompilatora clang, więc nie myli się jak narzędzia robiące zwykłą analizę, a nawet wspiera przepływ w programie, więc wydaje się najlepszym do sprawdzania stylu kodu. Co kluczowe jest to narzędzie nie tyle do sprawdzania stylu, ale do jego ustawiania automatycznie. Wspiera ono kod napisany w wielu językach programowania C/C++/Java/JavaScript/Objective-C/Protobuf/C#. Dla C++ ma wbudowane pewne przedefiniowane style (LLVM, Google, Chromium, Mozilla, WebKit), ale można tworzyć własne.

Narzędzie domyślnie wyszukuje w katalogu roboczym pliku .clang-format lub _clang-format. Tworzenie własnego stylu na bazie istniejących jest proste:
clang-format -style=llvm -dump-config > .clang-format
, potem wystarczy pozmieniać opcje konfiguracyjne stylu, których jest bardzo dużo.

Przykładowe użycia

clang-format file.cpp > file_formatted.cpp
clang-format -style=LLVM file.cpp > file_formatted2.cpp
clang-format -style="{BasedOnStyle: llvm, IndentWidth: 8}" file.cpp > file_formatted3.cpp
clang-format -style=fileWithStyle file.cpp > file_formatted4.cpp

Wybrane argumenty uruchomienia

  • -i
     - formatowanie pliku w miejscu
  • -lines=<start line>:<end line>
     - formatowanie tylko wybranych linii
  • -sort-includes
     - sortowanie nagłówków
  • -dump-config
     - wyświetlenie aktualnej konfiguracji stylu na standardowe wyjście
  • (uruchomiony bez argumentów) - czyta kod ze standardowego wejścia i formatuje na standardowe wyjście

Praca z diffami z repozytorium

Jeśli mamy niespójny kod, a nie mamy chęci ujednolicić w całym programie stylu i równocześnie nie chcemy, aby styl programu się nadal rozjeżdżał, mamy możliwość formatowania tylko diffów z repozytorium. Są wspierane repozytoria: git, hg, svn. Do tego celu używamy dostarczonego skryptu clang-format-diff.py:
git diff -U0 --no-color HEAD^ | clang-format-diff.py -i -p1

Bibliografia ClangFormat

Strona domowa
Istniejące opcje konfiguracji stylu

KWStyle

Jeśli powyższe rozwiązanie, które automatycznie poprawia styl, nam nie wystarcza, możemy się skusić na użycie narzędzia wyłącznie sprawdzającego styl autorstwa twórców CMake'a:

Narzędzie konfiguruje się przez plik XML, domyślnie jest to plik KWStyle.xml, mając go wywołujemy narzędzie w następujący sposób:
KWStyle MyFile.cpp
. Możemy też podać ścieżkę do pliku z konfiguracją stylu:
KWStyle -xml XMLDescriptionFile.xml MyFile.cpp
. Aby zacząć używać narzędzia polecam ze strony dokumentacji pobrać sobie przykładowy plik z ustawieniami i coś pozmieniać, posiłkować się można listą zaimplementowanych funkcjonalności.

Wybrane argumenty uruchamiania

  • -xml
     plikKonfigurujacyStyl.xml - podajemy ścieżkę do pliku konfigurującego styl
  • -o plikZDeaktywacjaRul.txt
     - dzięki niemu możemy wyłączyć dla poszczególnych plików poszczególne reguły
  • -html ścieżkaDoKatalogu
     - generowanie raportu w formie plikow HTML
  • -d katalog
     - zamiast pojedynczych plików analizuje cały katalog (domyślnie nierekurencyjnie)
  • -R
     - jeśli mamy opcje
    -d
     to dany katalog będzie przejrzany rekurencyjnie
  • -b czarnaListaPlikow.txt
     - pliki, które mają nie być analizowane

Bibliografia

Strona domowa
Oficjalna dokumentacja
Lista zaimplementowanych funkcjonalności

Inne narzędzia do sprawdzania stylu kodu

Tutaj tylko wspomnę, że narzędzi do tego celu jest wiele i zrzeszają społeczności użytkowników. Polecam przejrzeć sobie sensowne porównanie istniejących narzędzie (niestety z 2010 roku).
Jeśli ktoś zastanawia się, jaki styl wybrać w projekcie, może pokusić się o użycie narzędzia UniversalindentGUI, w którym może przejrzeć różne style na przykładowym kodzie. Niestety narzędzie to nie jest rozwijane, a w obecnych czasach najsensowniejsze wydaje się używanie oficjalnego stylu C++ wg wytycznych komitetu standaryzacyjnego.

Narzędzie do wielorakiej analizy z duża ilością funkcji -Sonar

Sonar to narzędzie, które używa wielu z wcześniej opisanych narzędzi i obsługuje wiele języków oprogramowania Java, JavaScript, C#, TypeScript, Kotlin, Ruby, Go, Scala, Flex, Python, PHP, HTML, CSS, XML and VB.NET, a także w płatnej wersji: Code iconC, C++, Obj-C, Swift, ABAP, T-SQL, PL/SQL. Na szczęście obsługuje też wtyczki, a tak się składa, że istnieje wtyczka do C++ rozwijana przez społeczność.
Sonar to tak naprawdę parę narzedzi SonarCube (to pobieramy), SonarCloud (do analizy kodu z repozytoriów), SonarLint (do integracji ze środowiskiem programistycznym np. VisualStudio).

Bibliografia

Oficjalna strona SonarCube
Reguły podlegające analizie w wersji płatnej dla kodu w C/C++
Lista reguł w różnych językach programowania z przykładami w wersji płatnej
Dokumentacja SonarCube (w tym jak instalować pluginy)
Bezpłatna wtyczka do C++ rozwijana przez społeczność (sprawdza mniej reguł niż wersja komercyjna)

Własne rozwiązania do analizy kodu

Możliwości jest wiele, najpopularniejszym prymitywnym narzędziem jest standardowy https://www.computerhope.com​/unix/ugrep.htm lub https://www.tutorialspoint.com​/sed/index.htm, dlatego warto znać ich możliwości. Warto też raz na jakiś czas odświeżać sobie znajomość programów unixowych, aby przyspieszać swoją pracę.
Z innych rozwiązań, które umożliwiają nie tylko sprawdzanie, ale też uzupełnianie pewnych informacji w plikach przed commitem są automatyczne hooki np. oferowane przez GITa.
W ramach hooków warto upewnić się, ze wrzucamy do repozytorium odpowiednie pliki, do tego warto użyć narzędzia file.

Analiza dynamiczna

Automatyczne wyszukiwanie błędów oprogramowaniu nie kończy się na analizie statycznej, bardzo istotna jest też analiza dynamiczna.

Debugger

Do podstawy w tym temacie należy debugger, mimo iż prawie nikt nie lubi go używać (zwłaszcza jeśli trzeba długo debuggować). Na szczęście jest wiele narzędzi graficznych, nawet wbudowanych w nasze IDE, które ułatwiają debuggowanie. W temacie debuggera podkreślam, że nawet jeśli używamy narzędzi graficznych, wygodniej jest poznać funkcjonalności swojego debuggera, aby móc optymalizować jego użycie. Przykładowo gdb ma mało znaną, ale przydatną możliwość nagrywania (a wtedy też cofania się) przebytych instrukcji oraz warunkowe zatrzymania.

Core dumpy

W razie awaryjnego przerwania działania programu wiele systemów operacyjnych generuje core-dumpy. Mając takiego core'a, można sprawdzić, gdzie nastąpiło awaryjne przerwanie działania programu. Robi się to w taki sposób (dla debuggera gdb):
gdb plikCoreDump
, następnie możemy zawołać komendę
bt
, ale czemu by nie zrobić tych dwóch rzeczy równocześnie? Robi się to tak:
gdb -silent list plikCoreDump --eval-command=bt --batch
, a jeśli chcemy tylko stos uruchomienia, możemy wygrepować:
gdb -silent list plikCoreDump --eval-command=bt --batch | grep -P ' at .+\..+:[[:digit:]]+$'
Oczywiście core-dumpy mają swoje limity, po przekroczeniu których nie są generowane. Limity można sprawdzić
ulimit -a
, a zmianę rozmiaru na np. nielimitowany zmieniamy:
ulimit -c unlimited
.
Dla wygody używania core-ów warto wiedzieć, gdzie się one znajdują. Możemy to sprawdzić
sysctl kernel.core_pattern
, a aby zmienić lokalizację możemy to zrobić w pliku /etc/sysctl.conf, oczywiście po tej zmianie należy przeładować ustawienia w systemie
sysctl -p
.
Szczegóły jak generować core'y w Twojej dystrybucji nalezy sprawdzić, gdyż niektóre systemy mają własny sposób na konfigurację coreów. Przykładowo Ubuntu używa programu Apport, który należy skonfigurować inaczej.

Valgrind

Jest to popularne narzędzie, znane z tego, że bada nasz program podczas wykonania pod kątem wycieków pamięci. Jednakże oprócz tego ma on kilka innych przydatnych funkcjonalności:
  • profiler przewidywania branchy i cache'u (Cachegrind i Callgrind)
  • wyszukiwanie błędów w wątkach m.in. zakleszczeń (Helgrind i moduł DRD)
  • profiler stosu (Helgrind)
  • przekroczenie zakresu tablic dla stosu i globalnych (eksperymentalny SGCheck)
  • profiler użycia sterty (eksperymentalny Massif i moduł DHAT)
  • "SimPoint basic block vector generator" - blok bazowy to sekcja kodu z jednym wejściem i wyjściem. Funkcjonalność rozpoznaje wiele takich vectorów, w które program wszedł w trakcie wykonywania. Użycie tego z założenia przyspiesza szczegółowe profilowanie nawet o ponad 90% (a profilowanie szczegółowe trwa rzędu 1000-krotnie wolniej niż zwykłe wykonanie). Dane te wziąłem z podstrony dokumentacji valgrinda poświęconej BBV
Aby użyć valgrinda, musimy mieć w skompilowanym programie odpowiednie informacje umożliwiające śledzenie wykonywania programu. Do tego celu np. dla kompilatora gcc stosuje się flagę
-ggdb3
.

Przykładowa komenda dla wycieków pamięci

valgrind --leak-check=yes --show-leak-kinds=all --track-origins=yes --track-fds=yes --error-exitcode=1 plikWykonywalny
Ewentualnie można sobie wyfiltrować po wykrytych błędach:
valgrind --leak-check=yes --show-leak-kinds=all --track-origins=yes --track-fds=yes --error-exitcode=1 | grep -P ' ERROR SUMMARY: 0 errors'

Bibliografia Valgrinda

Oficjalna strona Valgrinda
Oficjalna dokumentacja Valgrinda
Jak zacząć - tutorial na oficjalnej stronie
Moduł MemoryCheck

Narzędzia do sprawdzania pokrycia testami

W czasach coraz powszechniejszego pisania testów, a nawet Test-Driven Development, cenne są narzędzia do automatycznego sprawdzania pokrycia testami. Tutaj tylko wymienię dwa bezpłatne:
OpenCppCoverage, który niestety działa tylko na Windowsie Vista+.
narzędzie Gcov posiadające również interfejs graficzny narzędzie Lcov - do wykrywania pokrycia przez Gtesta.

Inne narzędzia

Lista przydatnych narzędzi nie jest zamknięta, jest ich bardzo dużo, o ciekawych funkcjonalnościach. Kilka przykładów znajduje się na stronie programisty-analityka Uniwersytetu Berkley w Kaliforni, ale to tylko przykłady.

Podsumowanie

Nie twierdzę, że powyższa lista jest listą najlepszych narzędzi do wykonywania danego zadania. Poza tym technologia idzie do przodu i najlepsze narzędzie w momencie pisania artykułu może już być przestarzałe w momencie, gdy czytasz ten artykuł. Poza tym chociażby stosować wszystkie istniejące narzędzia do automatycznego wyszukiwania błędów, nie mamy pewności, że nasze oprogramowanie jest wolne od błędów. Istotne jest to, że zastosowanie zbyt dużej ilości narzędzi będzie irytować programistów. Dlatego polecam każdemu z zespołów, aby bezwzględnie znał udogodnienia aktualnie używanego standardu i kompilatora, a resztę stosował zgodnie ze zdrowym rozsądkiem.
autor artykułuafiliacja
Grzegorz BaziorDoktorant AGH w Krakowie