Analisando código-fonte C/C++ com a ferramenta Cppcheck
- por Sergio Prado
Cppcheck é uma ferramenta de análise estática de código para aplicações escritas em C/C++ (se você nunca teve contato com uma ferramenta de análise estática de código, dê uma olhada no artigo Análise estática de código que escrevi algum tempo atrás).
O objetivo principal da ferramenta é encontrar erros que normalmente não são identificados pelo compilador, incluindo:
- Dereferenciamento de um ponteiro não inicializado.
- Acesso fora dos limites de um vetor.
- Utilização de variáveis não inicializadas.
- Vazamento de recursos (arquivos abertos mas não fechados, memória alocada mas não desalocada, etc).
- Avisos sobre código não utilizado ou duplicado.
É um projeto de código-aberto, no momento hospedado no Sourceforge, com suporte aos sistemas operacionais GNU/Linux, Windows e Mac.
INSTALANDO
No Linux, o Cppcheck pode ser instalado através da ferramenta de gerenciamento de pacotes da sua distribuição. Por exemplo, para o Ubuntu 14.04 basta executar o comando abaixo:
$ sudo apt-get install cppcheck |
A única desvantagem de utilizar a versão empacotada pela distribuição é o fato da versão disponibilizada ser mais antiga. No momento em que escrevo este artigo, a versão mais atual do Cppcheck é a 1.70, porém nos repositórios oficiais do Ubuntu está disponível apenas a versão 1.61.
Portanto, para utilizar a versão mais atual da ferramenta, basta baixar e compilar o código-fonte:
$ git clone git://github.com/danmar/cppcheck.git $ cd cppcheck/ $ git checkout 1.70 $ make CFGDIR=/etc/cppcheck/ HAVE_RULES=yes $ sudo CFGDIR=/etc/cppcheck/ make install |
Após a compilação, a versão instalada pode ser verificada com o comando abaixo:
$ cppcheck --version Cppcheck 1.70 |
TESTANDO
Vamos agora testar a ferramenta.
O código-fonte abaixo possui uma função para calcular a soma de todos os elementos de um vetor de inteiros. E ele tem dois problemas que não são identificados pela maioria dos compiladores, incluindo o GCC. Você consegue identificá-los?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void calc(void) { int buf[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int result; int i; for (i = 0; i <= 10; i++) { result += buf[i]; } } int main(void) { calc(); return 0; } |
Conseguiu? Aposto que o Cppcheck consegue identificar os problemas mais rapidamente! :)
$ cppcheck test.c Checking test.c... [test.c:8]: (error) Array 'buf[10]' accessed at index 10, which is out of bounds. [test.c:8]: (error) Uninitialized variable: result |
O primeiro erro está relacionado ao acesso fora dos limites do vetor buf, e o segundo erro está relacionado ao uso da variável result antes de inicializá-la.
O pior é que o GCC compila o código sem nenhuma mensagem de erro!
$ gcc -Wall -Wextra -Werror -Wpedantic test.c -o test |
Por padrão, o Cppcheck irá procurar apenas por erros. Checagens adicionais podem ser habilitadas através do parâmetro enable, incluindo mensagens de aviso, estilo de codificação, problemas de performance e portabilidade (mais informações sobre estas checagens na página de manual do Cppcheck).
$ man cppcheck |
O parâmetro enable=all permite realizar todas as checagens existentes:
$ cppcheck --enable=all test.c Checking test.c... [test.c:8]: (style) Variable 'result' is assigned a value that is never used. [test.c:8]: (error) Array 'buf[10]' accessed at index 10, which is out of bounds. [test.c:8]: (error) Uninitialized variable: result |
Veja que ele realizou um teste adicional de estilo de codificação, indicando que a variável result nunca é utilizada pela aplicação.
À propósito, o parâmetro doc permite exibir todas as checagens que podem ser realizadas pelo Cppcheck:
$ cppcheck --doc |
BUSYBOX
Para testar a ferramenta em uma outra aplicação, decidi executá-la em um projeto de médio porte. Baixei a versão 1.23.2 do Busybox e rodei o Cppcheck para analisar seu código-fonte:
$ cppcheck . >busybox.log 2>&1 |
Foram analisados no total 723 arquivos em pouco mais de dois minutos, com 74 erros identificados pela ferramenta.
$ cat busybox.log | grep "(error)" [applets/applet_tables.c:171]: (error) Resource leak: fp [archival/libarchive/decompress_bunzip2.c:162]: (error) Uninitialized variable: runCnt [coreutils/dos2unix.c:45]: (error) Uninitialized variable: temp_fn [coreutils/dos2unix.c:46]: (error) Uninitialized variable: resolved_fn [coreutils/expr.c:326]: (error) Uninitialized variable: l [coreutils/expr.c:327]: (error) Uninitialized variable: v [coreutils/test.c:633]: (error) Uninitialized variable: i [editors/awk.c:2500]: (error) Uninitialized variable: L_d ... |
Analisando algumas das mensagens de erro, encontrei alguns falso-positivos, mas também alguns problemas que precisam ser corrigidos ou trechos de código que podem ser refatorados.
ESTENDENDO O CPPCHECK
O Cppcheck pode ser estendido criando-se novas regras de checagem com expressões regulares ou mesmo através de módulos escritos em Python.
Existem também plugins do Cppcheck para diversas ferramentas de desenvolvimento populares como o Eclipse, Visual Studio, Code::Blocks, Sublime Text e QtCreator.
Além é claro do suporte ao Vim através do plugin Syntastic! :)
O manual da ferramenta está disponível online no site do projeto ou em formato PDF.
Conclusão? O Cppcheck é uma ferramenta extremamente útil e simples de usar. Integre-o ao sistema de build da sua aplicação e utilize-o em todos os seus projetos desenvolvidos em C/C++. Esta ferramenta não vai resolver todos os seus problemas, mas com certeza irá auxiliar na melhoria da qualidade do código-fonte da sua aplicação, identificar alguns problemas na sua origem e diminuir o tempo que você passa corrigindo bugs.
Happy coding!
Sergio Prado