Perl 5: como as macros ocultam erros


Para complementar a lista de linguagens de programação de código aberto que foram testadas usando o analisador de código estático PVS-Studio, foi selecionado o Perl 5. Este artigo trata de erros e dificuldades encontrados na visualização dos resultados da análise. O número de macros no código é tão grande que parece que o código não está escrito em C, mas em algum dialeto estranho dele. Apesar das dificuldades ao visualizar o código, consegui coletar problemas interessantes, que serão discutidos neste artigo.

1. Introdução


Perl é uma linguagem de programação dinâmica de propósito geral interpretada de alto nível (Perl é uma família de duas linguagens de programação dinâmica de alto nível, de propósito geral, interpretadas). Perl 5 foi lançado em 1994. Depois de algumas décadas, o código C com várias macros causa nervosismo aos programadores modernos.

O código fonte do Perl 5 foi retirado do repositório oficial ( blead branch). Para verificar o projeto, usamos o analisador de código estático PVS-Studio . A análise foi realizada no sistema operacional Linux, mas o analisador também está disponível para Windows e macOS.

Visualizar os resultados da análise não foi uma tarefa fácil. O fato é que o analisador verifica os arquivos .i pré - processados , nos quais todas as diretivas do pré-processador já estão abertas, e gera avisos nos arquivos com o código-fonte. Esse é o comportamento correto do analisador, você não precisa alterar nada, mas muitos avisos são emitidos para macros! E atrás das macros está um código ilegível.

O operador ternário não funciona da maneira que você pensa


V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o 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)) { .... } .... } 

Vamos começar a revisão com um belo erro. A cada poucas revisões de código, é necessário repetir que o operador ternário tem quase a menor prioridade na computação.

Considere o fragmento com o erro:

 s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1 

A ordem das operações que o programador espera:
  1. ?:
  2. -
  3. <=

O que realmente acontece:
  1. -
  2. <=
  3. ?:

Mantenha um rótulo com as prioridades de operações: " Prioridade de operações em C / C ++ ."

V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o 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 simples com um erro semelhante. Mas se você não souber as prioridades das operações, poderá cometer um erro em uma expressão de qualquer tamanho.

Outro local com afirmação:

  • V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '=='. re_exec.c 9286

V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '&&'. pp_hot.c 3036

 PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... } 

E aqui está um aviso para uma macro ... Para entender o que está acontecendo lá, mesmo a implementação da macro não ajudará, porque usa mais algumas macros!

Portanto, anexo um fragmento do arquivo pré-processado para esta linha 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)); 

Em algum lugar aqui, o analisador duvidava do uso correto do operador ternário (existem três), mas não encontrei forças para entender o que está sendo feito neste código. Já vimos que os desenvolvedores cometem esses erros, então aqui também pode ser muito provável.

Mais três usos dessa macro:

  • V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '&&'. pp_ctl.c 324
  • V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '&&'. regexec.c 7335
  • V502 Talvez o operador '?:' Funcione de maneira diferente do esperado. O operador '?:' Tem uma prioridade mais baixa que o operador '&&'. re_exec.c 7335

Nota colegas Andrei Karpov. Eu meditei nesse código por 10 minutos e estou inclinado a acreditar que não há erro. Mas, em qualquer caso, é extremamente doloroso ler esse código e é melhor não escrever assim.

Erros nas condições


V523 A instrução 'then' é equivalente à instrução '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); } 

Eu acho que você pode fazer isso sem examinar o conteúdo das macros para garantir que haja fragmentos de código duplicados suspeitos.

V564 O '|' O operador é aplicado ao valor do tipo bool. Você provavelmente esqueceu de incluir parênteses ou pretende usar o '||' 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 muito estranho. A expressão "iscv | ! (kid-> op_private & OPpCONST_ENTERED) "não é usado de forma alguma. Há claramente um erro de digitação aqui. Por exemplo, talvez você deva escrever aqui:

 : iscv = !(kid->op_private & OPpCONST_ENTERED), iscv // <= 

A expressão V547 'RETVAL == 0' sempre é verdadeira. 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); } 

A variável RETVAL é verificada duas vezes seguidas. Além disso, pode ser visto pelo código que essa variável é sempre zero. Talvez em uma ou ambas as condições queiram verificar o ponteiro RETVALSV , mas cometeram um erro de digitação.

Lançando avisos sobre o tamanho do operador


Existem vários tipos de regras de diagnóstico no analisador que procuram erros usando o operador sizeof . No projeto Perl 5, dois desses diagnósticos geraram um total de cerca de mil avisos. Nesse caso, não é o analisador que deve culpar, mas as macros.

V568 É estranho que o argumento do operador sizeof () seja a expressão 'len + 1'. util.c 1084

 char * Perl_savepvn(pTHX_ const char *pv, I32 len) { .... Newx(newaddr,len+1,char); .... } 

O código possui muitas macros semelhantes. Eu escolhi um como exemplo, estamos interessados ​​no argumento "len + 1".

Um pré-processador de macro se expande para o seguinte 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)))))); 

Um aviso do analisador é emitido para a construção sizeof (len +1) . O fato é que nenhum cálculo é feito nos argumentos do tamanho do operador. Muitas macros são expandidas em código semelhante. Provavelmente, esse é um código antigo antigo, no qual ninguém deseja tocar em nada, mas os desenvolvedores atuais continuam usando macros antigas, sugerindo um comportamento diferente.

Desreferenciando ponteiros nulos


V522 A desreferenciação do ponteiro nulo 'sv' pode ocorrer. 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) ? .... .... } .... } 

Esse fragmento de código é retirado completamente do arquivo pré-processado, porque é impossível verificar a presença do problema no arquivo de origem, novamente por causa das macros.

O ponteiro sv é inicializado com zero na declaração. O analisador descobriu que, ao passar acima de 5 na instrução switch , esse ponteiro é desreferenciado, o que nunca foi inicializado antes. Uma mudança no ponteiro sv está presente na ramificação com um valor de 4 , mas no final desse bloco de código está a instrução break . Muito provavelmente, é necessário código adicional neste local.

V595 O ponteiro 'k' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 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) ) { .... } 

Nesse trecho de código, o analisador encontrou um ponteiro k , que é desreferenciado uma linha antes da verificação de validade. Isso pode ser um erro ou um código extra.

O Diagnostics V595 encontra muitos avisos em qualquer projeto, o Perl 5 não é uma exceção. É impossível incluir tudo isso no artigo, portanto, nos limitaremos a um exemplo, e os desenvolvedores verificarão o projeto por conta própria, se assim o desejarem.

Diversos


V779 Código inacessível detectado. É possível que haja um erro. 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; } 

Na linha com XSRETURN_EMPTY, o analisador detectou um código inacessível. Há duas instruções de retorno nessa função e croak_xs_usage - uma macro que é expandida para uma função noreturn:

 void Perl_croak_xs_usage(const CV *const cv, const char *const params) __attribute__((noreturn)); 

Em locais semelhantes no código Perl 5, a macro NOT_REACHED é usada para indicar uma ramificação inacessível.

V784 O tamanho da máscara de bit é menor que o tamanho do primeiro operando. Isso causará a perda de bits mais 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; .... } 

O analisador detectou uma operação suspeita enquanto trabalhava com máscaras de bits. Como máscara de bit, uma variável de resolução mais baixa é usada do que a variável hold . Isso resulta na perda de bits altos. Os desenvolvedores devem prestar atenção a este código.

Conclusão


Quadro 6



Encontrar erros através de macros foi muito difícil. A visualização do relatório levou muito tempo e esforço. No entanto, o artigo inclui casos muito interessantes semelhantes a erros reais. O relatório do analisador é bastante grande, definitivamente há muito mais interessante lá. Mas não consigo mais olhar :). Aconselho o desenvolvedor a verificar o projeto por conta própria e eliminar os defeitos que eles serão capazes de detectar.

PS: Nós definitivamente queremos apoiar este projeto interessante e estamos prontos para fornecer aos desenvolvedores uma licença por vários meses.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Svyatoslav Razmyslov. Perl 5: Como ocultar erros em macros

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


All Articles