Perl 5: como las macros ocultaron los errores


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

Lo que realmente pasa:
  1. -
  2. <=
  3. ?:

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 // <= ? 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); .... } 

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) { // <= if (RETVAL == 0) // <= sv_setpvn(RETVALSV, "0 but true", 10); else sv_setiv(RETVALSV, (IV)RETVAL); } ST(0) = RETVALSV; } } XSRETURN(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); // <= 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) ) { .... } 

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


Cuadro 6



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

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


All Articles