Clang a statická analýza kódu

Obrázek uživatele b42

Cílem tohoto postu je ukázat použití statického analyzátoru, který je součástí kompilátoru Clang, pro nalezení chyby v céčkovém zdrojáku.

Clang/LLVM

O LLVM a Clangu jste už možná slyšeli. LLVM (Low Level Virtual Machine) je framework pro tvorbu kompilátorů. Umožňuje provádění optimalizací a generování kódu pro cílový procesor, čímž zjednodušuje tvorbu kompilátoru - stačí napsat front-end, který převede vstupní jazyk do vnitřního reprezentace jazyka LLVM a zbytek dostanete skoro zdarma. Je psaný velmi modulárně, takže s ním lze dělat spoustu zajímavých věcí jako je přidání vlastních optimalizací, nebo kompilace do binárky, co poběží na grafické kartě nebo FPGA.

Clang je kompilátor C/ObjC/C++ postavený nad LLVM. Krom toho, co byste od kompilátoru očekávali, si klade za cíl lepší hlášení chyb a podobně jako LLVM velkou modularitu. Ta umožňuje další neobvyklá použití, jako třeba zvýrazňování/doplňování syntaxe v editoru pomocí stejného kódu, který ho ve skutečnosti bude parsovat, nebo právě statickou analýzu, o které tady chci psát.

Statická analýza

Statická analýza souhrnně označuje techniky pro automatické hledání chyb ve zdrojovém kódu, aniž by tento kód byl spuštěn. Omezená forma statické analýzy často probíhá při běžné kompilaci, a jako výsledek kompilátor vypíše varování.

Statické analyzátory jdou v tomhle o něco dále a dokáží najít i složitější chyby. Je ovšem třeba mít na vědomí, že:

  • dokáží nalézt pouze (některé) chyby určitého typu, pro které byly napsány
  • false positives - ne vše, co statický analyzátor najde je skutečná chyba
  • analýza může trvat mnohem delší dobu než kompilace.

Asi nejznámějším statickým analyzátorem je Coverity, který je sice komerčním produktem, ovšem za peníze americké vlády je používán i pro hledání chyb v open-source projektech.

Existuje také několik open-source nástrojů pro statickou analýzu, ale nemyslím si, že některý z nich by byl široce rozšířen. V tomto postu bych se chtěl věnovat statickému analyzátoru, který je součástí Clangu (Clang Static Analyzer, dále jej budu označovat jako CSA), o kterém si myslím, že vypadá zajímavě a že by v budoucnu mohl být užitečný.

Instalace

CSA není přibalen k archivům s binárkami Clangu, takže je nutné jej zkompilovat ze zdrojového kódu. Popíšu instalaci aktuální verze ze SVN, kompilace z posledního oficiálního vydání LLVM by měla být podobná, akorát místo obsahu SVN stáhnete tyto dva archivy.

Instalace je popsána na domovské stránce projektu, takže jen v rychlosti:

$ cd ~/whatever
$ svn checkout http://llvm.org/svn/llvm-project/llvm/trunk llvm
$ cd llvm/tools
$ svn checkout http://llvm.org/svn/llvm-project/cfe/trunk clang
(poznámka: pokud instalujete z archivu, přejmenujte adresář clang-2.8 na clang)
$ cd ../..
$ ./configure
$ make
$ make install
(nebo si můžete udělat balíček)

Dále je vhodné umístit samotný analyzátor (tj. programy scan-build a scan-view) někam do $PATH, osobně na to používám adresář ~/bin:

$ cd ~/bin
$ ln -s ~/whatever/llvm/tools/clang/tools/scan-build/scan-build .
$ ln -s ~/whatever/llvm/tools/clang/tools/scan-view/scan-view .

Použití

Použití CSA bych chtěl ukázal na svém oblíbeném hudebním přehrávači, mocp.

CSA se spouští pomocí příkazu scan-build. Konkrétně scan-build [příkaz] se chová stejně jako spuštění [příkaz]u, s tím rozdílem, že spuštění C kompilátoru je nahrazeno spuštěním kompilátoru s následným spuštěním CSA. Kompilace tedy proběhne normálním způsobem, akorát se nad každým zdrojovým souborem kromě kompilátoru spustí i analyzátor.

U projektů používajících autotools, jako například mocp, provedeme analýzu následovně:

$ scan-build ./configure
$ scan-build make

(první příkaz je nutný, protože scan-build pracuje přenastavením proměnných prostředí, jejichž obsah je pak ./configure skriptem uložen)

Toť vše. Kompilace potrvá déle a na obrazovce se budou vypisovat informace navíc, ale těch není potřeba si zvláště všímat. Pokud vše půjde dobře, budou nás zajímat až poslední dva řádky:

scan-build: 12 bugs found.
scan-build: Run 'scan-view /tmp/scan-build-2011-01-30-1' to examine bug reports.

Výsledek analýzy si můžeme prohlédnout spuštěním uvedeného příkazu, nebo nasměrováním svého webového prohlížeče na /tmp/scan-build-2011-01-30-1.

Vidíme, že CSA nalezl 12 (možných) chyb různého typu. Podívejme se teď na jednu z chyb "Branch condition evaluates to a garbage value", konkrétně na tu, u které je Path Length, tedy délka cesty k chybě, rovno tři. Kliknutím na View Report se dostaneme na zobrazení zdrojového kódu, ve kterém je pomocí očíslovaných štítků popsána cesta, která k chybě vede.

Chyba se nachází ve funkci, která načítá konfigurační soubor. Z popisu, který vyprodukoval CSA vyplývá, že chyba nastane, když:

  • je otevřen konfigurační soubor
  • tento soubor je prázdný, a tedy se ani jednou neprovede tělo while-smyčky
  • za smyčkou zjišťujeme hodnotu prvního prvku pole opt_name, který ale nebyl inicializován, a tedy může obsahovat cokoli.

Tahle chyba sice není kdovíjak zajímavá - když je přehrávač spuštěn s prázdným konfiguračním souborem, nedeterministicky buď zahlásí chybu, nebo pokračuje s defaultní konfigurací - ale obecně je příkladem chování, které je v programu nežádoucí a kterému chceme předejít.

Chybu můžeme opravit buď inicializací první položky pole před tím, než je použito, nebo se místo hodnoty prvního prvku můžeme zeptat na délku řetězce, která je uložená v proměnné name_pos a která je správně inicializována na nulu. Opravením této chyby se zbavíme všech tří hlášení o chybách z kategorie "Branch condition evaluates to a garbage value". Nyní se podívejme na chybu označenou jako "Argument with 'nonnull' attribute passed null".

Problém zde spočívá v tom, že funkci execve je jako argument předán první prvek pole args, který je NULL, v případě, že jsme se k volání dostali po vyznačené cestě. Pokud si ale cestu dobře prohlédneme, uvidíme, že předpokádá neprázdnost seznamu on_song_change, ale zároveň také to, že for-smyčka, která iteruje nad prvky tohoto seznamu se ani jednou neprovede. To ve skutečnosti nemůže nastat a jedná se tedy o "falešný poplach" - false positive.

Zbytek chyb je ze skupiny "Dead Store", což znamená, že se nějaká spočítaná hodnota uloží do proměnné, ale pak už se nikdy nečte. To samo o sobě nemusí být chyba, ale může to poukázat na jiný problém (programátor použil obsah jiné proměnné než chtěl).

Závěr

Přestože statická analýza není samospasitelná a CSA toho zatím neumí tolik kolik by mohl, myslím si, že má potenciál stát se prakticky používaným nástrojem. Pokud vás CSA zaujal, více informací najdete na jeho domovské stránce.

Připomínky (nejen) ke gramatické stránce vítány;)