Análise estática de código

Em 28/01/2010, em Ferramentas, Linguagem C, por Sergio Prado

Diz o ditado que, no processo de desen­volvi­mento de soft­ware, pas­samos 50% do tempo cor­rigindo bugs, e os out­ros 50% inserindo estes bugs — do inglês “In the soft­ware devel­op­ment process, we pass 50% debug­ging, and the other 50% bugging”.

A mel­hor forma de perder menos tempo na cor­reção de prob­le­mas, é não criar prob­le­mas em primeiro lugar. Mas somos humanos, e errar é humano. Por­tanto, vamos errar. Invari­avel­mente, vamos acabar inserindo alguns bugs no código. Por­tanto, pre­cisamos de fer­ra­men­tas que min­i­mizem a prob­a­bil­i­dade destes erros acon­te­cerem. Uma destas fer­ra­men­tas é o próprio compilador.

Mas na prática, não é assim que a maio­ria dos desenvolvedores/engenheiros enx­ergam esta fer­ra­menta. Segundo estes, o obje­tivo do com­pi­lador é gerar o arquivo executável/binário, e ponto final. E aquele monte de warn­ings que o com­pi­lador “cus­piu” na tela? É facil de resolver, basta desabil­i­tar um parâmetro do com­pi­lador. Pronto, com­pi­lou sem nen­hum warning.

Mas os warn­ings con­tin­uam lá. O com­pi­lador bem que ten­tou te avisar: olhe, é bem provável que aquele pon­teiro não tenha sido ini­cial­izado antes de você ten­tar usá-lo para ref­er­en­ciar alguma região de memória, ou então, preste atenção, esta var­iável é char (1 byte) e você esta ten­tando atribuir um inteiro de 2 bytes a ela.

Porque então não inve­stir 10 min­u­tos do seu tempo para checar as men­sagens de warn­ing do com­pi­lador? É muito menos cus­toso do que depois perder 4 horas para achar um prob­lema que uma fer­ra­menta pode­ria ter encon­trado auto­mati­ca­mente para você. É extrema­mente impor­tante criar o hábito de checar os warn­ings de compilação.

Eu sei, você pode dizer que o com­pi­lador é muito chato, e alguns warn­ings, ou a maio­ria, não são críti­cos ou não irão causar nen­hum tipo de prob­lema para a apli­cação. O que eu posso te dizer é o seguinte: ninguém entende mel­hor da lin­guagem, sua sin­taxe e semân­tica, do que o com­pi­lador. Por­tanto, não tente ser mais esperto do que ele. O obje­tivo então, e prin­ci­pal­mente para quem tra­balha com sis­temas embar­ca­dos, é desen­volver um código extrema­mente portável, limpo, e o mais próx­imo pos­sível do padrão ANSI. Desta forma, o com­pi­lador vai ser muito mais “bonz­inho” com você.

O que pre­cisamos então é de uma fer­ra­menta que possa gerar mais warn­ings, não menos. Uma fer­ra­menta que analise o código e indique con­struções estra­nhas, poten­ci­ais prob­le­mas e usos não comuns da lin­guagem. Con­forme já men­cionei, alguns com­pi­ladores pos­suem uma boa parte desta fun­cional­i­dade, como o gcc através dos parâmet­ros “Wall”, “Wex­tra” e “pedan­tic” Mas exis­tem fer­ra­men­tas especi­fi­cas para este tra­balho. A estas fer­ra­men­tas damos o nome genérico de LINT.

Um artigo muito bom sobre o uso de LINT em sis­temas embar­ca­dos, suas van­ta­gens e como usá-la den­tro do processo de desen­volvi­mento de soft­ware pode ser encon­trado aqui. Nosso obje­tivo aqui é exem­pli­ficar através do uso de fer­ra­men­tas disponíveis no mercado.

Fer­ra­men­tas

O que as pes­soas nor­mal­mente recla­mam deste tipo de fer­ra­menta é a quan­ti­dade exces­siva de warn­ings ger­a­dos, muitos deles falso-positivos.

Pode-se chegar a mais de 10.000 warn­ings para um codigo de 1.000 lin­has. Por isso, a fer­ra­menta pre­cisa ser treinada — leia-se con­fig­u­rada — antes de uti­lizada. E este processo pode levar um tempo. Um bom tempo na ver­dade. Mas as rec­om­pen­sas são grandes no final.

Exis­tem muitas fer­ra­men­tas disponíveis, tanto livres  quanto com­er­ci­ais. Uma lista bem com­pleta pode ser encon­trada aqui. Para efeitos demon­stra­tivos vamos uti­lizar a fer­ra­menta PC-LINT.

O PC-LINT é com­er­cial, e uma das mais usadas fer­ra­men­tas de análise estática de codigo para C/C++. Para efeito de testes, vamos uti­lizar um recurso disponivel online no site da fer­ra­menta, onde é pos­sivel tes­tar tre­chos de codigo sem que seja necessário baixar e insta­lar a ferramenta.

Vamos tes­tar aqui 2 tre­chos difer­entes de código, um desen­volvido especi­fi­ca­mente para os testes e um reti­rado de um pacote open-source usado em sis­temas embar­ca­dos com Linux.

Teste 1: O jogo dos 4 erros

O pro­grama abaixo tem o obje­tivo de var­rer um vetor de números, somar ape­nas os números pares, e exibir o resul­tado. Você é bom naquele jogo dos 7 erros? Então tente encon­trar aqui os 4 erros que inseri neste código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
int soma(int a, int b) {
    return (a + b);
}
 
int subtrai(int a, int b) {
    return (a - b);
}
 
int main() {
 
    int ind, ret;
    char buf[10] = {15,24,37,42,53,67,79,81,94,6};
    char result;
 
    for (ind = 0; ind <= 10; ind++) {
        if ((buf[ind] % 2) = 0)
            result = soma(buf[ind], result);
    }
    printf("Soma = %d\n", result);
 
    return(0);
}
</stdio.h>

Você encon­trou os erros? Quanto tempo você levou? A fer­ra­menta encon­trou os erros de forma instantânea:

  • Warn­ing 661:  Pos­si­ble access of out-of-bounds pointer (1 beyond end of data) by oper­a­tor ‘[‘ [Ref­er­ence: file diy.c: lines 17, 18]
  • Info 774:  Boolean within ‘if’ always eval­u­ates to False [Ref­er­ence: file diy.c: line 18]
  • Info 734:  Loss of pre­ci­sion (assign­ment) (31 bits to 7 bits)
  • Warn­ing 530:  Sym­bol ‘result’ (line 15) not initialized

Seguem os erros, na ordem das men­sagens acima:

  1. Estou ultra­pas­sando os lim­ites do buffer “buf” ao acessá-lo no loop com a var­iável “ind”. Na linha 17 dev­e­ria tro­car “ind<=10″ por “ind<10″.
  2. Na com­para­ção usada na linha 18, “aci­den­tal­mente” dig­itei “=” ao invés de “==”.
  3. Na linha 19, estou atribuindo um inteiro a um byte. A var­iável “result” dev­e­ria ser, no min­imo, um inteiro.
  4. A vari­avel “result” não foi ini­cial­izada com o valor 0 antes de ini­ciar a soma.

Além destes erros, a fer­ra­menta ainda apon­tou alguns warn­ings de sím­bo­los (var­iáveis e funções) não uti­liza­dos, e poderíamos uti­lizar estas dicas para deixar o código mais claro e limpo:

Warn­ing 529:  Sym­bol ‘ret’ (line 13) not sub­se­quently ref­er­enced Info 714:  Sym­bol ‘subtrai(int, int)’ (line 7, file diy.c) not referenced

Teste 2: Fer­ra­menta Date do Busy­box

Este teste foi feito com o pacote open-source Busy­box. O Busy­box é muito uti­lizado em sis­temas embar­ca­dos com Linux, pois pro­por­ciona um con­junto de fer­ra­men­tas comu­mente uti­lizadas em Linux, porém bem mais enx­u­tas que suas ver­sões orig­i­nais. Os testes foram feitos com o módulo date.c.

A fer­ra­menta não encon­trou nen­hum erro crítico, mas algu­mas men­sagens de warn­ing foram exibidas, e uma análise e revisão do código pode­ria deixá-lo bem mais limpo e portável. Algu­mas das men­sagens infor­madas pela ferramenta:

  • Loss of sign (assign­ment) (int to unsigned int)
  • Type mis­match (assign­ment) (char * = int)
  • Use of goto is dep­re­cated Sym­bol ‘argc’ (line 41) not ref­er­enced Loss of pre­ci­sion (arg. no. 1) (unsigned int to int)

Que ver­gonha hein galera do soft­ware livre? Usando goto???

Minha con­clusão é a seguinte: se você tem 4 horas para cor­tar uma árvore, afie seu machado por 3 horas, e em menos de uma hora esta árvore estará cor­tada. Às vezes achamos que vamos perder muito tempo até apren­der a usar ou con­fig­u­rar uma fer­ra­menta, mas não percebe­mos que este inves­ti­mento é valido, pois o tempo que perdemos depois para cor­ri­gir os prob­le­mas é muito maior. Sem con­tar que o apren­dizado durante o processo vale, e muito, a pena.

Um abraço a todos,

Ser­gio Prado

VN:F [1.9.17_1161]
Rat­ing: 9.5/10 (4 votes cast)
Análise estática de código, 9.5 out of 10 based on 4 ratings

Posts rela­ciona­dos:

  1. Otimiza­ção de código em Lin­guagem C — Parte 1
  2. Otimiza­ção de código em Lin­guagem C — Parte 2
  • Tiago

    Bom artigo Sérgio.

    Você que já está mais famil­iar­izado com este tipo de fer­ra­menta, qual você indica? (que seja freeware)

    Abraços

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • http://www.embarcados.com.br Ser­gio Prado

    Olá Tiago,

    Já usei o Splint (http://splint.org/), que é um fork do antigo lint desen­volvido para UNIX, e que traz alguns con­ceitos extras para o desen­volvi­mento de soft­ware seguro. è muito bom e bem fácil de con­fig­u­rar e utilizar.

    Já ouvi tam­bém falarem muito bem do Blast mas nunca cheguei a utilizá-lo (http://mtc.epfl.ch/software-tools/blast/index-epfl.php). Me parece ser um pouco mais com­plexo, porém mais flexível também.

    Um abraço!

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • ener­gos

    Não acho que o uso de goto seja nec­es­sari­a­mente uma ver­gonha. O prob­lema é o uso abu­sivo dele, gerando código “spaghetti”. Como toda fer­ra­mente, se bem usada, pode ser útil.
    Tem uma dis­cussão inter­es­sante sobre isso em http://kerneltrap.org/node/553/2131

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • http://www.embarcados.com.br Ser­gio Prado

    Olá ener­gos,

    Entendo per­feita­mente seu ponto de vista. Toda fer­ra­menta pode ser tanto efi­ciente quanto ine­fi­ciente, depende do uso que faze­mos dela. No caso do goto em par­tic­u­lar, é uma questão de estilo.

    Já vi casos ele­gantes do uso de goto, prin­ci­pal­mente para o trata­mento de erros no estilo try-except, como tam­bém já vi inúmeros casos de imple­men­tações bem ao estilo “lin­guiça” ou “espagueti”.

    Par­tic­u­lar­mente, eu pre­firo um código com funções peque­nas e bem definidas, e um con­t­role de fluxo com if, for e do/while. Esses ele­men­tos são sufu­cientes para desen­volver um código em C ele­gante, sim­ples, portável e de fácil manutenção.

    Legal você ter lev­an­tado esta questão, acho que esse assunto até vale um post separado…:)

    Um abraço!

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • Pingback: Tweets that mention Análise estática de código em sistemas embarcados | Blog do Sergio Prado -- Topsy.com

  • Pingback: Netrino's Embedded C Coding Standard

  • Paulo

    Sér­gio,
    muito legal seu post, acom­panho seu blog, sem­pre com ótimos arti­gos, uma duvida, na linha de código if ((buf[ind] % 2) = 0)

    era uma atribuição que você gostaria de fazer mesmo?
    abracos

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • http://www.sergioprado.org ser­gio­prado

    Olá Paulo!

    Não, a intenção era fazer uma com­para­ção. O que eu que­ria mostrar é um erro comum de pro­gra­mação em C, que pode ser iden­ti­fi­cado facil­mente por uma fer­ra­menta de análise estática de código.

    Um abraço!

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • Paulo

    ah, sim des­culpe, eu vi o código e postei um comen­tário antes de ter­mi­nar de ler o texto! =(
    desculpe

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)