Perl 5: comment les macros masquaient les erreurs


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:
  1. ?:
  2. -
  3. <=

Ce qui se passe vraiment:
  1. -
  2. <=
  3. ?:

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 // <= ? SVt_PVCV : o->op_type == OP_RV2SV ? SVt_PV : o->op_type == OP_RV2AV ? SVt_PVAV : o->op_type == OP_RV2HV ? SVt_PVHV : SVt_PVGV); .... } 

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) { // <= if (RETVAL == 0) // <= sv_setpvn(RETVALSV, "0 but true", 10); else sv_setiv(RETVALSV, (IV)RETVAL); } ST(0) = RETVALSV; } } XSRETURN(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); // <= if ( k // <= && k->op_type == OP_KEYS && ( want == OPf_WANT_VOID || want == OPf_WANT_SCALAR) && !(k->op_private & OPpMAYBE_LVSUB) && !(k->op_flags & OPf_MOD) ) { .... } 

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; /* local strm->hold */ unsigned bits; /* local strm->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


Image 6



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

Source: https://habr.com/ru/post/fr425415/


All Articles