Pour compléter la liste des langages de programmation open source qui ont été testés à l'aide de l'analyseur de code statique PVS-Studio, Perl 5 a été sélectionné. Cet article traite des erreurs détectées et des difficultés à visualiser les résultats de l'analyse. Le nombre de macros dans le code est si grand qu'il semble que le code ne soit pas écrit en C, mais dans un étrange dialecte. Malgré les difficultés lors de la visualisation du code, j'ai réussi à collecter des problèmes intéressants, qui seront abordés dans cet article.
Présentation
Perl est un langage de programmation polyvalent dynamique interprété de haut niveau (Perl est une famille de deux langages de programmation dynamique interprétés de haut niveau). Perl 5 a été lancé en 1994. Après quelques décennies, le code C avec de nombreuses macros inquiète les programmeurs modernes.
Le code source de Perl 5 provient du
dépôt officiel (branche
blead ). Pour vérifier le projet, nous avons utilisé l'analyseur de code statique
PVS-Studio . L'analyse a été effectuée sur le système d'exploitation Linux, mais l'analyseur est également disponible pour Windows et macOS.
La visualisation des résultats de l'analyse n'a pas été une tâche facile. Le fait est que l'analyseur vérifie les fichiers
.i prétraités , dans lesquels toutes les directives du préprocesseur sont déjà ouvertes, et génère des avertissements sur les fichiers avec le code source. C'est le comportement correct de l'analyseur, vous n'avez rien à changer, mais de nombreux avertissements sont émis pour les macros! Et derrière les macros se trouve un code illisible.
L'opérateur ternaire ne fonctionne pas comme vous le pensez
V502 L'opérateur '?:'
Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité inférieure à l'opérateur '-'. toke.c 9494
STATIC char * S_scan_ident(pTHX_ char *s, char *dest, STRLEN destlen, I32 ck_uni) { .... if ((s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1) && VALID_LEN_ONE_IDENT(s, PL_bufend, is_utf8)) { .... } .... }
Commençons l'examen avec une belle erreur. Toutes les quelques révisions de code doivent répéter que l'opérateur ternaire a presque la priorité la plus basse en informatique.
Considérez le fragment avec l'erreur:
s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1
L'ordre des opérations que le programmeur attend:
- ?:
- -
- <=
Ce qui se passe vraiment:
- -
- <=
- ?:
Gardez une étiquette avec les priorités des opérations: "
Priorité des opérations en C / C ++ ".
V502 L'opérateur '?:'
Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité inférieure à l'opérateur '=='. re_exec.c 9193
STATIC I32 S_regrepeat(pTHX_ regexp *prog, char **startposp, const regnode *p, regmatch_info *const reginfo, I32 max _pDEPTH) { .... assert(STR_LEN(p) == reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1); .... }
Code simple avec une erreur similaire. Mais si vous ne connaissez pas les priorités des opérations, vous pouvez vous tromper dans une expression de n'importe quelle taille.
Un autre endroit avec assert:
- V502 L'opérateur '?:' Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité inférieure à l'opérateur '=='. re_exec.c 9286
V502 L'opérateur '?:'
Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité plus faible que l'opérateur '&&'. pp_hot.c 3036
PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... }
Et voici un avertissement pour une macro ... Pour comprendre ce qui se passe là-bas, même l'implémentation de la macro n'aidera pas, car elle utilise encore quelques macros!
Par conséquent, je joins un fragment du fichier prétraité pour cette ligne de code:
(((targ)->sv_flags & 0x00000400) && (!((targ)->sv_flags & 0x00200000) || S_sv_only_taint_gmagic(targ)) ? (mg)->mg_len = ((prog->offs)[0].end), (mg)->mg_flags |= 0x40 : ((mg)->mg_len = (((targ)->sv_flags & 0x20000000) && !__builtin_expect(((((PL_curcop)->cop_hints + 0) & 0x00000008) ? (_Bool)1 :(_Bool)0),(0))) ? (ssize_t)Perl_utf8_length( (U8 *)(truebase), (U8 *)(truebase)+((prog->offs)[0].end)) : (ssize_t)((prog->offs)[0].end), (mg)->mg_flags &= ~0x40));
Quelque part ici, l'analyseur doutait de l'utilisation correcte de l'opérateur ternaire (il y en a 3), mais je n'ai pas trouvé la force de comprendre ce qui est fait dans ce code. Nous avons déjà vu que les développeurs font de telles erreurs, donc ici, cela peut également être très probable.
Trois autres utilisations de cette macro:
- V502 L'opérateur '?:' Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité plus faible que l'opérateur '&&'. pp_ctl.c 324
- V502 L'opérateur '?:' Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité plus faible que l'opérateur '&&'. regexec.c 7335
- V502 L'opérateur '?:' Fonctionne peut-être d'une manière différente de celle attendue. L'opérateur '?:' A une priorité plus faible que l'opérateur '&&'. re_exec.c 7335
Notez les collègues Andrei Karpov. J'ai médité sur ce code pendant 10 minutes et j'ai tendance à croire qu'il n'y a pas d'erreur. Mais en tout cas, il est extrêmement pénible de lire un tel code, et il vaut mieux ne pas écrire comme ça.
Erreurs dans les conditions
V523 L'
instruction 'then' est équivalente à l'instruction 'else'. toke.c 12056
static U8 * S_add_utf16_textfilter(pTHX_ U8 *const s, bool reversed) { .... SvCUR_set(PL_linestr, 0); if (FILTER_READ(0, PL_linestr, 0)) { SvUTF8_on(PL_linestr); } else { SvUTF8_on(PL_linestr); } PL_bufend = SvEND(PL_linestr); return (U8*)SvPVX(PL_linestr); }
Je pense que vous pouvez vous passer de l'examen du contenu des macros pour vous assurer qu'il y a des fragments de code dupliqués de manière suspecte.
V564 Le '|' L'opérateur est appliqué à la valeur de type bool. Vous avez probablement oublié d'inclure des parenthèses ou avez l'intention d'utiliser le '||' opérateur. op.c 11494
OP * Perl_ck_rvconst(pTHX_ OP *o) { .... gv = gv_fetchsv(kidsv, o->op_type == OP_RV2CV && o->op_private & OPpMAY_RETURN_CONSTANT ? GV_NOEXPAND : iscv | !(kid->op_private & OPpCONST_ENTERED), iscv
Code très bizarre. L'expression "iscv | ! (kid-> op_private & OPpCONST_ENTERED) "n'est utilisé en aucune façon. Il y a clairement une faute de frappe ici. Par exemple, vous devriez peut-être écrire ici:
: iscv = !(kid->op_private & OPpCONST_ENTERED), iscv
V547 L' expression 'RETVAL == 0' est toujours vraie. Typemap.c 710
XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass); XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass) { dVAR; dXSARGS; if (items != 0) croak_xs_usage(cv, ""); { SysRet RETVAL; #line 370 "Typemap.xs" RETVAL = 0; #line 706 "Typemap.c" { SV * RETVALSV; RETVALSV = sv_newmortal(); if (RETVAL != -1) {
La variable
RETVAL est vérifiée deux fois de suite. De plus, il ressort du code que cette variable est toujours nulle. Peut-être dans une ou les deux conditions, ils voulaient vérifier le pointeur
RETVALSV , mais ils ont fait une faute de frappe.
Lancer des avertissements sur la taille de l'opérateur
Il existe plusieurs types de règles de diagnostic dans l'analyseur qui recherchent les erreurs à l'aide de l'opérateur
sizeof . Dans le cadre du projet Perl 5, deux de ces diagnostics ont généré un total d'environ mille avertissements. Dans ce cas, ce n'est pas l'analyseur qui est à blâmer, mais les macros.
V568 Il est étrange que l'argument de l'opérateur sizeof () soit l'expression 'len + 1'. util.c 1084
char * Perl_savepvn(pTHX_ const char *pv, I32 len) { .... Newx(newaddr,len+1,char); .... }
Le code a beaucoup de macros similaires. J'en ai choisi un comme exemple, nous sommes intéressés par l'argument "len + 1".
Un préprocesseur de macro se développe dans le code suivant:
(newaddr = ((void)(__builtin_expect(((((( sizeof(size_t) < sizeof(len+1) || sizeof(char) > ((size_t)1 << 8*(sizeof(size_t) - sizeof(len+1)))) ? (size_t)(len+1) : ((size_t)-1)/sizeof(char)) > ((size_t)-1)/sizeof(char))) ? (_Bool)1 : (_Bool)0),(0)) && (S_croak_memory_wrap(),0)), (char*)(Perl_safesysmalloc((size_t)((len+1)*sizeof(char))))));
Un avertissement d'analyseur est émis pour la construction
sizeof (len +1) . Le fait est qu'aucun calcul n'est fait dans les arguments de l'opérateur
sizeof . De nombreuses macros sont développées dans un code similaire. Il s'agit probablement d'un ancien code hérité dans lequel personne ne veut rien toucher, mais les développeurs actuels continuent d'utiliser d'anciennes macros, suggérant leur comportement différent.
Déréférencer les pointeurs nuls
V522 Le déréférencement du pointeur nul «sv» peut avoir lieu. pp_ctl.c 577
OP * Perl_pp_formline(void) { .... SV *sv = ((void *)0); .... switch (*fpc++) { .... case 4: arg = *fpc++; f += arg; fieldsize = arg; if (mark < sp) sv = *++mark; else { sv = &(PL_sv_immortals[2]); Perl_ck_warner( (28 ), "...."); } .... break; case 5: { const char *s = item = ((((sv)->sv_flags & (....)) == 0x00000400) ? .... .... } .... }
Ce fragment de code est entièrement extrait du fichier prétraité, car il est impossible de vérifier la présence du problème dans le fichier source, encore une fois à cause des macros.
Le pointeur
sv est initialisé à zéro lors de la déclaration. L'analyseur a découvert qu'en passant plus de
5 dans l'
instruction switch , ce pointeur est déréférencé, ce qui n'a jamais été initialisé auparavant. Un changement dans le pointeur
sv est présent dans la branche avec une valeur de
4 , mais à la fin de ce bloc de code se trouve l'instruction
break . Très probablement, un code supplémentaire est requis à cet endroit.
V595 Le pointeur 'k' a été utilisé avant d'être vérifié par rapport à nullptr. Lignes de contrôle: 15919, 15920. op.c 15919
void Perl_rpeep(pTHX_ OP *o) { .... OP *k = o->op_next; U8 want = (k->op_flags & OPf_WANT);
Dans cet extrait de code, l'analyseur a trouvé un pointeur
k , qui est déréférencé une ligne avant sa vérification de validité. Cela peut être une erreur ou un code supplémentaire.
Diagnostics
V595 trouve beaucoup d'avertissements dans n'importe quel projet, Perl 5 ne fait pas exception. Il est impossible d’intégrer tout cela dans l’article, nous nous limiterons donc à un seul exemple et les développeurs vérifieront le projet par eux-mêmes s’ils le souhaitent.
Divers
V779 Code inaccessible détecté. Il est possible qu'une erreur soit présente. universal.c 457
XS(XS_utf8_valid); XS(XS_utf8_valid) { dXSARGS; if (items != 1) croak_xs_usage(cv, "sv"); else { SV * const sv = ST(0); STRLEN len; const char * const s = SvPV_const(sv,len); if (!SvUTF8(sv) || is_utf8_string((const U8*)s,len)) XSRETURN_YES; else XSRETURN_NO; } XSRETURN_EMPTY; }
Sur la ligne avec
XSRETURN_EMPTY, l' analyseur a détecté un code inaccessible. Il existe deux
instructions de retour dans cette fonction et
croak_xs_usage - une macro qui est développée en fonction noreturn:
void Perl_croak_xs_usage(const CV *const cv, const char *const params) __attribute__((noreturn));
Dans des endroits similaires dans le code Perl 5, la macro
NOT_REACHED est utilisée pour indiquer une branche inaccessible.
V784 La taille du masque de bits est inférieure à la taille du premier opérande. Cela entraînera la perte de bits supérieurs. inffast.c 296
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { .... unsigned long hold; unsigned bits; .... hold &= (1U << bits) - 1; .... }
L'analyseur a détecté une opération suspecte lors de l'utilisation de masques de bits. En tant que masque de bits, une variable de résolution inférieure est utilisée que la variable
hold . Il en résulte la perte de bits élevés. Les développeurs doivent faire attention à ce code.
Conclusion

Trouver des erreurs via des macros a été très difficile. La consultation du rapport a demandé beaucoup de temps et d'efforts. Néanmoins, l'article comprend des cas très intéressants qui sont similaires à de vraies erreurs. Le rapport de l'analyseur est assez volumineux, il y a certainement beaucoup plus intéressant là-bas. Mais je ne peux pas regarder plus loin :). Je conseille au développeur de vérifier lui-même le projet et d'éliminer les défauts qu'il pourra détecter.
PS Nous voulons vraiment soutenir ce projet intéressant, et nous sommes prêts à fournir aux développeurs une licence pour plusieurs mois.

Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Svyatoslav Razmyslov.
Perl 5: comment masquer les erreurs dans les macros