Analyze et C ...

laudemalaudema Membre
mai 2014 modifié dans Xcode et Developer Tools #1

Bonjour,


 


Dans la foulée de mon passage à  ARC, je me suis résolu à  supprimer, aussi, les erreurs détectées quand on utilise Analyze dans le menu Product (de Xcode).


J'ai su traiter toutes les erreurs dans mon code mais je bute sur 3 erreurs dans un fichier écrit en C que j'ai récupéré sur internet et qui sert à  parser des fichiers au format .ini .


 


La première erreur est "Call to 'malloc' has an allocation of 0 bytes" 



int     seclen, nkeys ;
nkeys = iniparser_getsecnkeys(d, s);
keys = (char**) malloc(nkeys*sizeof(char*)); <= Warning "Call to 'malloc' has an allocation of 0 bytes"

Pourquoi penser que nkeys serait égal à  0 ?


 


Une autre erreur est "Value stored to 'sta' is never read". Alors que 'sta' sera la valeur de retour de la fonction et que c'est d'ailleurs le but de cette fonction que de calculer la valeur de cette variable !.



//....
line_status sta;
//...
 sta = LINE_UNPROCESSED ;  <= Warning "Value stored to 'sta' is never read"
    if (len<1) {
        /* Empty line */
        sta = LINE_EMPTY ;
    } else if (line[0]=='#' || line[0]==';') {
        /* Comment line */
        sta = LINE_COMMENT ; 
    } else if (line[0]=='[' && line[len-1]==']') {
        /* Section name */
        sscanf(line, "[%[^]]", section);
        strcpy(section, strstrip(section));
        strcpy(section, strlwc(section));
        sta = LINE_SECTION ;
    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
           ||  sscanf (//....
//.....
return sta ;

D'accord elle n'est pas lue ici mais elle le sera à  la sortie, on a pas fait ça pour rien ?.


 


La troisième est comme la seconde "Value stored to 'last' is never read" sauf que cette fois la variable 'last' n'est pas renvoyée comme résultat de la fonction mais utilisée (donc lue) dans un while(){} à  l'intérieur de la fonction



char line    [ASCIILINESZ+1]  ;
int last=0 ;
 while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
//....
/* Detect multi-line */
        if (line[len]=='\\') {
            /* Multi-line value */
            last=len ;
            continue ;
        } else {
            last=0 ; <= Warning "Value stored to 'last' is never read" ?
        }
//..
}

J'avoue avoir du mal à  comprendre parfois le C mais là  je ne vois pas comment faire taire ces warnings.


Réponses

  • AliGatorAliGator Membre, Modérateur
    Quand tu as des erreurs de l'Analyzer, pense à  cliquer sur la bulle bleue de l'erreur pour qu'il te montre le chemin de l'arbre qu'il a pris.

    L'erreur qu'il remonte est en général un truc qu'il a détecté que dans le cas où il passe par là  puis par là  puis par là .

    Par exemple pour le cas de "Value stored to 'sta' is never read" si dans ton bloc "if/else if/else if/..." il n'y a pas de "else" tout court, ça veut dire qu'il existe un cas (celui où tous les "if" sont à  false et qu'aucun ne passe donc tu ne passes dans aucune condition) qui n'est pas traité (c'est ce cas qui va t'être mis en avant avec des jolies flèches bleues quand tu cliques sur la bulle bleue pour te montrer par où il est passé).
    Par exemple si tu as un cas genre "if (a==1) {...} else if (a>2) { ...} else if (a<1) { ... }", le cas "a==2" n'est pas traité, et l'Analyzer va te remonter que tu ne couvres pas tous les cas. Or dans ce cas là , ta variable "sta" n'est du coup même pas initialisée (tu ne lui donnes pas de valeur par défaut) et tu risques de retourner du grand n'importe quoi.
  • AliGatorAliGator Membre, Modérateur

    La première erreur est "Call to 'malloc' has an allocation of 0 bytes" 


    int     seclen, nkeys ;
    nkeys = iniparser_getsecnkeys(d, s);
    keys = (char**) malloc(nkeys*sizeof(char*)); <= Warning "Call to 'malloc' has an allocation of 0 bytes"
    Pourquoi penser que nkeys serait égal à  0 ?

    Pourquoi penser que nkeys pourrait ne pas être égal à  zéro ? Rien ne te garantit que iniparser_getsecnkeys() ne risque pas de retourner 0 (par exemple un fichier .ini vide ?)
  • AliGatorAliGator Membre, Modérateur

    La troisième est comme la seconde "Value stored to 'last' is never read" sauf que cette fois la variable 'last' n'est pas renvoyée comme résultat de la fonction mais utilisée (donc lue) dans un while(){} à  l'intérieur de la fonction

    Là  encore, l'Analyzer va te montrer le chemin suivi dans lequel il te remonte cette erreur. Il a identifié que "il existe un cas où la variable last n'est jamais lue" du coup je suppose (si jamais tu passes pas dans le while, ou tu n'y passes qu'une fois, ou je sais pas quoi, faut regarder le chemin bleu qu'il t'indique et les étapes qu'il a identifié pour mener à  ce cas (de la même manière que les flèches bleues il t'affiche aussi le détail des cas où il passe point par point les unes après les autres)
  • AliGatorAliGator Membre, Modérateur
    Par exemple sur ce cas (image trouvée au hasard sur Google Images)
    xPnmr.png
    Il t'indique qu'il passe par les différentes lignes qu'on voit avec le chemin montré via les flèches, et il indique que si on suit ce chemin particulier, alors à  un moment (étape "1.") la variable "currentNode" est initialisée à  "nil" et que du coup en conséquence un peu plus tard (étape "2. ") on essaye d'accéder à  la variable d'instance "nodeX" de ce "currentNode" ce qui a pour conséquence de tenter de déréférencer un pointeur NULL

    Bref avec cet exemple tu vois que l'Analyzer peut te montrer les différentes étapes qui l'ont conduit à  trouver cette erreur, erreur qui n'arrive que si certaines conditions sont réunions, mais c'est bien là  l'intérêt du Statuc Analyzer, c'est d'analyser tous les chemins / branches de code que tu peux prendre et tous les cas de valeurs possibles pour voir s'il n'y a pas des cas particuliers où, si certaines conditions sont réunions, alors ça posera problème.
  • laudemalaudema Membre
    mai 2014 modifié #6

    Merci Ali,


     


    Mais quand j'ai commencé à  analyser mon code j'avais plus de 150 erreurs... Du coup le parcours fléché j'ai l'habitude, il y en a même eu de très esthétiques ;).


    Mais là  j'ai beau cliquer de mon mieux il trouve rien à  me montrer, juste le warning sur la ligne qui scintille mais pas de flèche qui se dessine..


  • AliGatorAliGator Membre, Modérateur
    Tu peux copier/coller ici le code complet de la fonction où tu as ce "line_status sta" ? (La flemme / pas le temps de télécharger le tar.gz et chercher à  la main)

    Si ça se trouve il y a une "shadow variable" par exemple.
  • laudemalaudema Membre
    mai 2014 modifié #8


    Tu peux copier/coller ici le code complet de la fonction où tu as ce "line_status sta" ? (La flemme / pas le temps de télécharger le tar.gz et chercher à  la main)


    Si ça se trouve il y a une "shadow variable" par exemple.




    Bien sûr


     


    En haut il y a ça



    /* Defines */
    #define ASCIILINESZ         (4028)
    #define INI_INVALID_KEY     ((char*)-1)
     


    /*                         Private to this module
      */
    /**
     * This enum stores the status for each parsed line (internal use only).
     */
    typedef enum _line_status_ {
        LINE_UNPROCESSED,
        LINE_ERROR,
        LINE_EMPTY,
        LINE_COMMENT,
        LINE_SECTION,
        LINE_VALUE
    } line_status ;
     
    /* */

    Puis vient la fonction, qui ne pose plus problème avec le #ifndef __clang_analyzer__



    /* */
    /**
      @brief ;   Load a single line from an INI file
      @param ;   input_line  Input line, may be concatenated multi-line input
      @param ;   section     Output space to store section
      @param ;   key         Output space to store key
      @param ;   value       Output space to store value
      @return   line_status value
     */
    /* */
    static line_status iniparser_line(
    #ifndef __clang_analyzer__
        const char * input_line,
        char * section,
        char * key,
        char * value)
    {
        static int compteur = 0;
        ++compteur;
        line_status sta ;
        char        line[ASCIILINESZ+1];
        int         len ;
     
        strcpy(line, strstrip(input_line));
        len = (int)strlen(line);
     
        sta = LINE_UNPROCESSED ;
        if (len<1) {
            /* Empty line */
            sta = LINE_EMPTY ;
        } else if (line[0]=='#' || line[0]==';') {
            /* Comment line */
            sta = LINE_COMMENT ; 
        } else if (line[0]=='[' && line[len-1]==']') {
            /* Section name */
            sscanf(line, "[%[^]]", section);
            strcpy(section, strstrip(section));
            strcpy(section, strlwc(section));
            sta = LINE_SECTION ;
        } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
               ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
               ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
            /* Usual key=value, with or without comments */
            strcpy(key, strstrip(key));
            strcpy(key, strlwc(key));
            strcpy(value, strstrip(value));
            /*
             * sscanf cannot handle '' or "" as empty values
             * this is done here
             */
            if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
                value[0]=0 ;
            }
            sta = LINE_VALUE ;
        } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
               ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
            /*
             * Special cases:
             * key=
             * key=;
             * key=#
             */
            strcpy(key, strstrip(key));
            strcpy(key,  strlwc(key));
            value[0]=0 ;
            sta = LINE_VALUE ;
        } else {
            /* Generate syntax error */
            sta = LINE_ERROR ;
        }
        return sta ;
    #endif
    }

    Au départ 'sta' vaut LINE_UNPROCESSED soit 0 et si aucune condition n'est remplie la fonction renvoie 0 et la variable n'a plus d'utilité.


    Mais je ne vois pas où est la faute.


     


    Une chose amusante est que d'avoir ajouté #ifndef /endif dans cette fonction a fait disparaà®tre le warning que j'avais concernant 'last'. 'last' est une variable située dans une autre fonction qui appelle iniparser_line() à  chaque ligne pour en connaà®tre le type avant de l'étudier.



    /* */
    /**
      @brief ;   Parse an ini file and return an allocated dictionary object
      @param ;   ininame Name of the ini file to read.
      @return   Pointer to newly allocated dictionary
     
      This is the parser for ini files. This function is called, providing
      the name of the file to be read. It returns a dictionary object that
      should not be accessed directly, but through accessor functions
      instead.
     
      The returned dictionary must be freed using iniparser_freedict().
     */
    /* */
    dictionary * iniparser_load(const char * ininame)
    {
        FILE * in ;
     
        char line    [ASCIILINESZ+1] ;
        char section [ASCIILINESZ+1] ;
        char key     [ASCIILINESZ+1] ;
        char tmp     [ASCIILINESZ+1] ;
        char val     [ASCIILINESZ+1] ;
     
        int  last=0 ;
        int  len ;
        int  lineno=0 ;
        int  errs=0;
     
        dictionary * dict ;
     
        if ((in=fopen(ininame, "r"))==NULL) {
            fprintf(stderr, "iniparser: cannot open %s\n", ininame);
            return NULL ;
        }
     
        dict = dictionary_new(0) ;
        if (!dict) {
            fclose(in);
            return NULL ;
        }
     
        memset(line,    0, ASCIILINESZ);
        memset(section, 0, ASCIILINESZ);
        memset(key,     0, ASCIILINESZ);
        memset(val,     0, ASCIILINESZ);
        last=0 ;
     
        while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
            lineno++ ;
            len = (int)strlen(line)-1;
            if (len==0)
                continue;
           
            /* Get rid of \n and spaces at end of line */
            while ((len>=0) &&
                    ((line[len]=='\n') || (isspace(line[len])))) {
                line[len]=0 ;
                len-- ;
            }
            /* Detect multi-line */
            if (line[len]=='\\') {
                /* Multi-line value */
                last=len ;
                continue ;
            } else {
                last=0 ;
            }
            switch (iniparser_line(line, section, key, val)) {
                case LINE_EMPTY:
                case LINE_COMMENT:
                break ;
     
                case LINE_SECTION:
                errs = dictionary_set(dict, section, NULL);
                break ;
     
                case LINE_VALUE:
                sprintf(tmp, "%s:%s", section, key);
                errs = dictionary_set(dict, tmp, val) ;
                break ;
     
                case LINE_ERROR:
                fprintf(stderr, "iniparser: syntax error in %s (%d):\n",
                        ininame,
                        lineno);
                fprintf(stderr, "-> %s\n", line);
                errs++ ;
                break;
     
                default:
                break ;
            }
            memset(line, 0, ASCIILINESZ);
            last=0;
            if (errs<0) {
                fprintf(stderr, "iniparser: memory allocation failure\n");
                break ;
            }
        }
        if (errs) {
            dictionary_del(dict);
            dict = NULL ;
        }
        fclose(in);
        return dict ;
    }
     
    /* */

    Si tu veux les fichiers pour ne pas avoir de déclarations manquantes ils sont 4 dans un dossier zippé :

  • AliGatorAliGator Membre, Modérateur
    mai 2014 modifié #9
    Ah ok bah j'ai compris l'erreur alors, elle est justifiée.

    En fait je croyais (quand je n'avais que le début de la fonction) que tu n'avais pas de "else" dans tes blocs "if/else if/else if" (et donc que si aucune de tes conditions "if" n'était justifiée, il ne rentrait dans aucun bloc, ce qui fait que le return aurait retourné la valeur initiale de sta, à  savoir LINE_UNPROCESSED.

    Mais il se trouve que tu as un "else" et donc que quel que soit le cas, tu passes forcément dans une des conditions de tes "if/else if/else if/else if/else" et que quelle que soit la condition qui correspond, dans tous les cas tu fais un "sta = XXX" dedans.

    Ce qui veut dire que à  la fin la valeur "LINE_UNPROCESSED" que tu mets dans la variable "sta" n'est JAMAIS utilisée, car elle n'est jamais lue pendant le corps de ta fonction et dans tous les cas elle est changée avant la fin de ta fonction pour avoir une autre valeur.
    C'est un peu comme si tu écrivais :
    int a = 7;
    if (condition) {
    a = 12;
    } else {
    a = 15;
    }
    // 'a' vaudra 12 ou 15, mais jamais 7, et l'affectation a=7 en début de code
    // ne sert strictement a rien, cette valeur 7 n'est jamais lue.

    Au départ 'sta' vaut LINE_UNPROCESSED soit 0 et si aucune condition n'est remplie la fonction renvoie 0 et la variable n'a plus d'utilité.

    Non justement vu que tu as un "else" il ne peux pas y avoir de "si aucune condition n'est remplie". Enfin plutôt si aucune des conditions "if" n'est remplie, il passe dans le "else" et affecte alors 'sta' à  une autre valeur LINE_ERROR et la fonction ne renvoie donc jamais LINE_UNPROCESSED car cette valeur est dans tous les cas overridée.

    Donc en gros:
    - soit tu enlèves la ligne "sta = LINE_UNPROCESSED" qui ne sert à  rien puisque dans tous les cas sta va être remplacée par une autre valeur dans un des "if" donc que la valeur sera toujours écrasée
    - soit tu rempalces la ligne par "sta = LINE_ERROR" et tu enlèves le "else", comme ça sta sera un peu "la valeur par défaut si aucune des conditions des if n'est remplie"
  • Là  ça marche, mais du coup, si j'enlève le ifndef __clang_analyzer__ dans cette fonction je me retrouve avec un warning dans la fonction iniparser_load(). En regardant mieux je vois que ça correspond à  un else qui ne servira pas et que je peux supprimer. Tout va mieux, merci Ali. 


    Cet analyzer est vraiment très puissant.


    Reste le cas du malloc(nkeys*sizeof(char*)) ou il estime que nkeys = 0 est possible alors que la ligne d'avant sert justement à  sortir s'il ne trouve pas d'entrée à  analyser.


    Un assert(nkeys>0) aurait été bien agréable. On trouve des NSAssert() dans le code exemple d'Apple mais c'est refusé par le préprocesseur car non autorisé avec C99 ..


  • AliGatorAliGator Membre, Modérateur

    Reste le cas du malloc(nkeys*sizeof(char*)) ou il estime que nkeys = 0 est possible alors que la ligne d'avant sert justement à  sortir s'il ne trouve pas d'entrée à  analyser.

    J'ai pas tout compris sur ce point là . Le code que tu cites plus haut est le suivant :
    int     seclen, nkeys ;
    nkeys = iniparser_getsecnkeys(d, s);
    keys = (char**) malloc(nkeys*sizeof(char*)); <= Warning "Call to 'malloc' has an allocation of 0 bytes"
    Or je ne vois pas où dans ce code il y a une quelconque protection pour vérifier si nkeys>0, donc quand tu dis "la ligne d'avant sert à  sortir s'il ne trouve pas" de quelle ligne tu parles ?
  • Le code lit ligne par ligne et construit un dictionnaire. S'il n'arrive pas à  détecter d'entrée il sort de la fonction et donc la ligne nkeys = .. n'est pas appelée. En fait la ligne juste avant que je ne coupe est



    if (! iniparser_find_entry(d, s)) return keys;

    À ce moment là  keys vaut 0...


    En même temps, si tu le sais pas tu peux pas le deviner :(


     


    Pour finir j'ai trouvé ce qui manquait pour indiquer qu'on était sûr (?) qu'il n'y aurait pas de probleme grâce à  une assertion.


    Il fallait juste faire un #include de <assert.h> !


    Ensuit j'ai pu écrire assert(nkeys>0) sans que le préprocesseur ne me dise que la déclaration implicite de fonction était interdite en C99. C'est là  où ça coinçait.


    Depuis plus de #ifndef __clang_analyzer__ et pas de warnings pour autant..


    Merci pour ton aide.


Connectez-vous ou Inscrivez-vous pour répondre.