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:
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:
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 kompilatoraGdy 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ą:
#include <iostream>
int main() { int a; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuninitialized" if( a ) std::cout << "a != 0!\n"; #pragma GCC diagnostic pop 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:
Ignorowanie raportowania błędów przez cppcheckKluczowa funkcjonalność - możliwość ignorowania sprawdzenia danego błędu dla pewnej instrukcji, aby to osiągnąć należy poprzedzić ją przez: Można też z komentarzem:
|
Cppcheck-guiW 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 dokumentacjaArtykuły opisujące działanie oraz tworzenie reguł do sprawdzania stylu programowaniaPrzykł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-tidyAby wyłączyć ostrzeżenia należy obok podejrzanej linijki wpisać w komentarzu np.: Oczywiście możemy też podać powód: Możemy też wyłączyć tylko konkretne ostrzeżenie: W przypadku dłuższych linijek możemy wyłączyć ostrzeżenia dla następnej linijki:
|
Bibliografia clang-tidy
Dokumentacja clang-tidylista 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
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-buildFlawfinder
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)
Celowe deaktywowanie raportowania błędówAutor 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:
|
Przykładowe uruchamianie
flawfinder file.cc
flawfinder --quiet --dataonly --nolink --columns --context --singleline --neverignore file.cc
Bibliografia Flawfinder'a
Strona domowaOficjalna dokumentacjaKsiążka on-line o zagrożeniach w kodzie ww. autoraStrona z najczęstszymi zagrożeniami bezpieczeństwa w kodzie
Common Weakness EnumerationNarzę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#include <vector> class ForwardDeclaration;
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 gccW 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 domowapliki dokumentacjiCopy-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
Uruchamianie GUIAby 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 CPDDecykowane 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 SplintNajnowsza dokumentacja narzędzia SplintInne dokumenty (w tym publikacje naukowe) poświęcone narzędziu SplintRepozytorium narzędzia SplintLizard - 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ż:
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
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, instrukcjaSourceCode 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:
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 domowaDokument 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 GNUStyl GoogleStyl Linusa Torvaldsa dla jądra Linux i
inne styleClang-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
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 domowaIstnieją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
Bibliografia
Strona domowaOficjalna dokumentacjaLista zaimplementowanych funkcjonalnościInne 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 SonarCubeReguł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łatnejDokumentacja 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:
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 ValgrindaOficjalna dokumentacja ValgrindaJak zacząć - tutorial na oficjalnej stronie
Moduł MemoryCheckNarzę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.