Analyze et C ...
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
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.
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.
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..
Si ça se trouve il y a une "shadow variable" par exemple.
Bien sûr
En haut il y a ça
Puis vient la fonction, qui ne pose plus problème avec le #ifndef __clang_analyzer__
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.
Si tu veux les fichiers pour ne pas avoir de déclarations manquantes ils sont 4 dans un dossier zippé :
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 :
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 ..
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
À 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.