Para complementar la lista de lenguajes de programación de código abierto que se probaron con el analizador de código estático PVS-Studio, se seleccionó Perl 5. Este artículo trata sobre los errores encontrados y las dificultades para ver los resultados del análisis. La cantidad de macros en el código es tan grande que parece que el código no está escrito en C, sino en algún dialecto extraño. A pesar de las dificultades al ver el código, logré recopilar problemas interesantes, que se discutirán en este artículo.
Introduccion
Perl es un lenguaje de programación dinámico de alto nivel interpretado de alto nivel (Perl es una familia de dos lenguajes de programación dinámico de alto nivel, interpretado y dinámico). Perl 5 se lanzó en 1994. Después de un par de décadas, el código C con numerosas macros causa nerviosismo para los programadores modernos.
El código fuente de Perl 5 fue tomado del
repositorio oficial (rama de
blead ). Para verificar el proyecto, utilizamos el analizador de código estático
PVS-Studio . El análisis se realizó en el sistema operativo Linux, pero el analizador también está disponible para Windows y macOS.
Ver los resultados del análisis no fue una tarea fácil. El hecho es que el analizador comprueba los archivos
.i preprocesados , en los que todas las directivas del preprocesador ya están abiertas, y genera advertencias en los archivos con código fuente. Este es el comportamiento correcto del analizador, no necesita cambiar nada, ¡pero se emiten muchas advertencias para las macros! Y detrás de las macros hay un código ilegible.
El operador ternario no funciona como usted piensa.
V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '-'. 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)) { .... } .... }
Comencemos la revisión con un hermoso error. Cada pocas revisiones de código tienen que repetir que el operador ternario tiene casi la prioridad más baja en informática.
Considere el fragmento con el error:
s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1
El orden de las operaciones que el programador espera:
- ?:
- -
- <=
Lo que realmente pasa:
- -
- <=
- ?:
Mantenga una etiqueta con las prioridades de las operaciones: "
Prioridad de las operaciones en C / C ++ ".
V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '=='. 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); .... }
Código simple con un error similar. Pero si no conoce las prioridades de las operaciones, puede cometer un error en una expresión de cualquier tamaño.
Otro lugar con afirmar:
- V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '=='. re_exec.c 9286
V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '&&'. pp_hot.c 3036
PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... }
Y aquí hay una advertencia para una macro ... Para comprender lo que está sucediendo allí, incluso la implementación de la macro no ayudará, ¡porque utiliza algunas macros más!
Por lo tanto, adjunto un fragmento del archivo preprocesado para esta línea de código:
(((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));
En algún lugar aquí, el analizador dudaba del uso correcto del operador ternario (hay 3 de ellos), pero no encontré la fuerza para comprender lo que se está haciendo en este código. Ya hemos visto que los desarrolladores cometen tales errores, por lo que aquí también puede ser muy probable.
Tres usos más de esta macro:
- V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '&&'. pp_ctl.c 324
- V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '&&'. regexec.c 7335
- V502 Quizás el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '&&'. re_exec.c 7335
Tenga en cuenta colegas Andrei Karpov. Medité en este código durante 10 minutos y me inclino a creer que no hay ningún error. Pero en cualquier caso, es extremadamente doloroso leer dicho código, y es mejor no escribir así.
Errores en las condiciones.
V523 La declaración 'then' es equivalente a la declaración '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); }
Creo que puede hacerlo sin examinar el contenido de las macros para asegurarse de que haya fragmentos de código duplicados sospechosamente.
V564 El '|' El operador se aplica al valor de tipo bool. Probablemente te hayas olvidado de incluir paréntesis o tengas la intención de usar '||' operador 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
Código muy raro La expresión "iscv | ! (kid-> op_private & OPpCONST_ENTERED) "no se utiliza de ninguna manera. Claramente hay un error tipográfico aquí. Por ejemplo, quizás deberías escribir aquí:
: iscv = !(kid->op_private & OPpCONST_ENTERED), iscv
V547 La expresión 'RETVAL == 0' siempre es verdadera. 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 se verifica dos veces seguidas. Además, del código se puede ver que esta variable siempre es cero. Quizás en una o ambas condiciones quisieron comprobar el puntero
RETVALSV , pero hicieron un error tipográfico.
Lanzando advertencias sobre el tamaño del operador
Existen varios tipos de reglas de diagnóstico en el analizador que buscan errores utilizando el operador
sizeof . En el proyecto Perl 5, dos de estos diagnósticos generaron un total de aproximadamente mil advertencias. En este caso, no es el analizador el culpable, sino las macros.
V568 Es extraño que el argumento del operador sizeof () sea la expresión 'len + 1'. util.c 1084
char * Perl_savepvn(pTHX_ const char *pv, I32 len) { .... Newx(newaddr,len+1,char); .... }
El código tiene muchas macros similares. Elegí uno como ejemplo, nos interesa el argumento "len + 1".
Un preprocesador de macro se expande en el siguiente código:
(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))))));
Se emite una advertencia del analizador para la construcción
sizeof (len +1) . El hecho es que no se realizan cálculos en los argumentos del operador
sizeof . Muchas macros se expanden en un código similar. Este es probablemente un antiguo código heredado en el que nadie quiere tocar nada, pero los desarrolladores actuales continúan usando macros antiguas, lo que sugiere su comportamiento diferente.
Desreferenciar punteros nulos
V522 Puede tener lugar la desreferenciación del puntero nulo 'sv'. 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) ? .... .... } .... }
Este fragmento de código se toma completamente del archivo preprocesado, porque es imposible verificar la presencia del problema desde el archivo fuente, nuevamente debido a las macros.
El puntero
sv se inicializa a cero tras la declaración. El analizador descubrió que al pasar más de
5 en la
instrucción de cambio , este puntero se desreferencia, que nunca se inicializó antes. Un cambio en el puntero
sv está presente en la rama con un valor de
4 , pero al final de este bloque de código se encuentra la instrucción
break . Lo más probable es que se requiera código adicional en este lugar.
V595 El puntero 'k' se utilizó antes de que se verificara contra nullptr. Líneas de verificación: 15919, 15920. op.c 15919
void Perl_rpeep(pTHX_ OP *o) { .... OP *k = o->op_next; U8 want = (k->op_flags & OPf_WANT);
En este fragmento de código, el analizador encontró un puntero
k , que se desreferencia una línea antes de su verificación de validez. Esto puede ser un error o un código extra.
Diagnostics
V595 encuentra muchas advertencias en cualquier proyecto, Perl 5 no es una excepción. Es imposible incluir todo esto en el artículo, por lo tanto, nos limitaremos a un ejemplo y los desarrolladores comprobarán el proyecto por su cuenta si lo desean.
Misceláneo
V779 Código inalcanzable detectado. Es posible que haya un error presente. 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; }
En la línea con
XSRETURN_EMPTY, el analizador detectó un código inalcanzable. Hay dos
declaraciones de retorno en esta función y
croak_xs_usage , una macro que se expande en una función de retorno:
void Perl_croak_xs_usage(const CV *const cv, const char *const params) __attribute__((noreturn));
En lugares similares en el código Perl 5, la macro
NOT_REACHED se usa para indicar una rama inalcanzable.
V784 El tamaño de la máscara de bits es menor que el tamaño del primer operando. Esto causará la pérdida de bits más altos. inffast.c 296
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { .... unsigned long hold; unsigned bits; .... hold &= (1U << bits) - 1; .... }
El analizador detectó una operación sospechosa mientras trabajaba con máscaras de bits. Como una máscara de bits, se utiliza una variable de resolución más baja que la
retención variable. Esto da como resultado la pérdida de bits altos. Los desarrolladores deben prestar atención a este código.
Conclusión

Encontrar errores a través de macros fue muy difícil. Ver el informe tomó mucho tiempo y esfuerzo. Sin embargo, el artículo incluye casos muy interesantes que son similares a los errores reales. El informe del analizador es bastante grande, definitivamente hay mucho más interesante allí. Pero no puedo verlo más :). Aconsejo al desarrollador que compruebe el proyecto por su cuenta y elimine los defectos que podrá detectar.
PD Definitivamente queremos apoyar este interesante proyecto, y estamos listos para proporcionar a los desarrolladores una licencia por varios meses.

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Svyatoslav Razmyslov.
Perl 5: Cómo ocultar errores en macros