Testando o compilador GCC 10 com PVS-Studio

PVS-Studio vs GCC 10

O compilador GCC é escrito com o uso abundante de macros. Outra verificação do código GCC usando o PVS-Studio confirma mais uma vez a opinião da nossa equipe de que as macros são ruins. Esse código é difícil de entender não apenas para um analisador estático, mas também para um programador. Obviamente, os desenvolvedores do GCC já estão acostumados ao projeto e são bem versados ​​nele. Mas por um lado é muito difícil entender alguma coisa. Na verdade, devido às macros, não foi possível executar completamente a verificação do código. No entanto, o analisador PVS-Studio, como sempre, mostrou que pode encontrar erros mesmo nos compiladores.

Hora de verificar novamente o código do compilador GCC


A última vez que verifiquei o compilador GCC há quatro anos. O tempo voa de forma rápida e imperceptível, e de alguma forma eu esqueci de voltar a esse projeto e verificar novamente. A publicação " Static analysis in GCC 10 " retornou a essa ideia .

Na verdade, não é segredo que os compiladores têm seus próprios analisadores de código estático e também estão desenvolvendo. Portanto, de tempos em tempos, escrevemos artigos que o analisador estático do PVS-Studio pode encontrar erros mesmo dentro de compiladores e que não somos em vão comendo pão :).

De fato, você não pode comparar analisadores estáticos clássicos com compiladores. Os analisadores estáticos não são apenas uma busca por erros no código, mas também uma infraestrutura desenvolvida. Por exemplo, isso é integração com sistemas como SonarQube, PlatformIO, DevOps do Azure, Travis CI, CircleCI, GitLab CI / CD, Jenkins, Visual Studio. Esses são mecanismos desenvolvidos para a supressão de avisos em massa, que permitem iniciar rapidamente o uso do PVS-Studio, mesmo em um grande projeto antigo. Esta é uma lista de email de notificação. E assim por diante. De qualquer forma, a primeira pergunta é: “O PVS-Studio pode encontrar algo que os compiladores não conseguem encontrar?”. Então, escreveremos artigos repetidamente sobre a verificação desses compiladores.

Vamos voltar ao assunto da verificação do projeto do GCC. Não há necessidade de me debruçar sobre este projeto e dizer o que é. Vamos conversar melhor o que há dentro deste projeto.

E por dentro há um grande número de macros que interferem na verificação. Em primeiro lugar, o analisador PVS-Studio gera um grande número de falsos positivos. Não há nada errado com isso, mas não é tão fácil tomar e começar a estudar o relatório emitido por ele. De uma maneira boa, você precisa trabalhar para suprimir avisos falsos em macros. Caso contrário, avisos úteis simplesmente se afogam em um fluxo de ruído. Essa configuração está além do escopo desta tarefa de redação de artigos. Na verdade, serei completamente honesto - fiquei com preguiça de fazer isso, embora não haja nada de complicado nisso. Devido ao barulho, visualizar o relatório foi bastante superficial.

Em segundo lugar, é muito difícil para mim, como pessoa não familiarizada com o projeto, entender o código. Macros, macros ... Você precisa examinar o que eles estão implantados para entender por que o analisador gera avisos. Muito difícil. Eu não gosto de macros . Alguém pode dizer que sem macros em C não pode fazer. Mas o GCC não foi escrito em C. por um longo tempo. Sim, os arquivos por motivos históricos têm a extensão .c, mas você olha para lá e para lá:

//  alias.c
....
struct alias_set_hash : int_hash <int, INT_MIN, INT_MIN + 1> {};
struct GTY(()) alias_set_entry {
  alias_set_type alias_set;
  bool has_zero_child;
  bool is_pointer;
  bool has_pointer;
  hash_map<alias_set_hash, int> *children;
};
....

Claramente, isso não é C, mas C ++.

Em geral, macros e estilo de codificação dificultam o estudo do relatório do analisador. Portanto, desta vez, não agradarei uma longa lista de vários erros. Com dificuldade e usando algumas xícaras de café, escrevi 10 fragmentos interessantes, e isso me deixou em paz :).

10 trechos de código suspeitos


Fragmento N1, parece copiar e colar malsucedido

static bool
try_crossjump_to_edge (int mode, edge e1, edge e2,
                       enum replace_direction dir)
{
  ....
  if (FORWARDER_BLOCK_P (s->dest))
    s->dest->count += s->count ();

  if (FORWARDER_BLOCK_P (s2->dest))
    s2->dest->count -= s->count ();
  ....
}

PVS-Studio Warning: V778 Dois fragmentos de código semelhantes foram encontrados. Talvez seja um erro de digitação e a variável 's2' deva ser usada em vez de 's'. cfgcleanup.c 2126

De fato, não tenho certeza se isso é um erro. No entanto, tenho uma forte suspeita de que esse código tenha sido escrito usando Copy-Paste e, no segundo bloco em um local, eles esqueceram de substituir s por s2 . Ou seja, parece-me que o segundo bloco de código deve ser assim:

if (FORWARDER_BLOCK_P (s2->dest))
  s2->dest->count -= s2->count ();

Fragmento N2, erro tipográfico

tree
vn_reference_lookup_pieces (....)
{
  struct vn_reference_s vr1;
  ....
  vr1.set = set;
  vr1.set = base_set;
  ....
}

PVS-Studio Warning: V519 A variável 'vr1.set' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 3448, 3449. tree-ssa-sccvn.c 3449

É muito estranho que valores diferentes sejam gravados na mesma variável duas vezes seguidas. Este é um erro de digitação óbvio. Ao lado deste arquivo está este código:

vr1.set = set;
vr1.base_set = base_set;

Muito provavelmente, no código suspeito, ele deveria ter sido escrito exatamente da mesma maneira.

Fragmento N3, Atribuindo uma variável a si mesma

static omp_context *
new_omp_context (gimple *stmt, omp_context *outer_ctx)
{
  omp_context *ctx = XCNEW (omp_context);

  splay_tree_insert (all_contexts, (splay_tree_key) stmt,
         (splay_tree_value) ctx);
  ctx->stmt = stmt;

  if (outer_ctx)
    {
      ctx->outer = outer_ctx;
      ctx->cb = outer_ctx->cb;
      ctx->cb.block = NULL;
      ctx->local_reduction_clauses = NULL;
      ctx->outer_reduction_clauses = ctx->outer_reduction_clauses;  // <=
      ctx->depth = outer_ctx->depth + 1;
    }
  ....
}

Aviso do PVS-Studio: V570 A variável 'ctx-> outer_reduction_clauses' é atribuída a si mesma. omp-low.c 935

É muito estranho atribuir uma variável a si mesma.

Fragmento N4. 0,1,2, Freddy vai buscá-lo.

Eu recentemente publiquei um artigo, " Zero, Um, Dois, Freddy Vai Te Levar ". Parece-me que o seguinte snippet de código continua a coleção de erros discutidos neste artigo.

#define GET_MODE(RTX)    ((machine_mode) (RTX)->mode)
....
static int
add_equal_note (rtx_insn *insns, rtx target, enum rtx_code code, rtx op0,
                rtx op1, machine_mode op0_mode)
{
  ....
  if (commutative_p
      && GET_MODE (xop0) != xmode0 && GET_MODE (xop1) != xmode1
      && GET_MODE (xop0) == xmode1 && GET_MODE (xop1) == xmode1)
    std::swap (xop0, xop1);
  ....
}

Aviso do PVS-Studio: V560 Uma parte da expressão condicional é sempre falsa:
((machine_mode) (xop1) -> mode) == xmode1. optabs.c 1053

Observe estas duas subexpressões:

  • GET_MODE (xop1)! = Xmode1
  • GET_MODE (xop1) == xmode1

A operação AND é executada nos resultados dessas subexpressões, que obviamente não têm significado prático. Na verdade, se a segunda subexpressão começar a ser executada, é sabido antecipadamente que resultará em falsa .

Provavelmente, existe um erro de digitação aqui em zeros e uns e, de fato, a condição deveria ter sido assim:

&& GET_MODE (xop0) != xmode0 && GET_MODE (xop1) != xmode1
&& GET_MODE (xop0) == xmode1 && GET_MODE (xop1) == xmode0

Obviamente, não tenho certeza se alterei o código corretamente, pois não sou orientado no projeto.

Fragmento N5. Alteração suspeita no valor do argumento

bool
ipa_polymorphic_call_context::set_by_invariant (tree cst,
                                                tree otr_type,
                                                HOST_WIDE_INT off)
{
  poly_int64 offset2, size, max_size;
  bool reverse;
  tree base;

  invalid = false;
  off = 0;                // <=
  ....
  if (otr_type && !contains_type_p (TREE_TYPE (base), off, otr_type))
    return false;

  set_by_decl (base, off);
  return true;
}

PVS-Studio Warning: V763 O parâmetro 'off' é sempre reescrito no corpo da função antes de ser usado. ipa-polymorphic-call.c 766

O valor do argumento off é imediatamente substituído por 0. Além disso, não há comentários explicativos. Tudo isso é muito suspeito. Às vezes, esse código aparece durante a depuração. O programador precisava ver como a função se comporta em um determinado modo; portanto, ele alterou temporariamente o valor do argumento e, em seguida, eles esqueceram de excluir essa linha. Como resultado, um erro aparece no código. Obviamente, tudo pode estar bem aqui, mas esse código precisa claramente ser verificado e esclarecido para garantir que perguntas semelhantes não surjam no futuro.

Fragmento N6. Pequena coisa

cgraph_node *
cgraph_node::create_clone (....)
{
  ....
  new_node->icf_merged = icf_merged;
  new_node->merged_comdat = merged_comdat;   // <=
  new_node->thunk = thunk;
  new_node->unit_id = unit_id;
  new_node->merged_comdat = merged_comdat;   // <=
  new_node->merged_extern_inline = merged_extern_inline;
  ....
}

Aviso do PVS-Studio: V519 A variável 'new_node-> merged_comdat' recebe valores duas vezes sucessivos. Talvez isso seja um erro. Verifique as linhas: 406, 409. cgraphclones.c 409 A atribuição é

duplicada aleatoriamente. Provavelmente nada de errado. No entanto, sempre existe o risco de que eles se esqueçam de realizar alguma outra tarefa.

Fragmento N7. Código que parece perigoso

static void
complete_mode (struct mode_data *m)
{
  ....
  if (m->cl == MODE_COMPLEX_INT || m->cl == MODE_COMPLEX_FLOAT)
    alignment = m->component->bytesize;
  else
    alignment = m->bytesize;

  m->alignment = alignment & (~alignment + 1);

  if (m->component)
  ....
}

Aviso do PVS-Studio: V595 O ponteiro 'm-> component' foi utilizado antes de ser verificado no nullptr. Verifique as linhas: 407, 415. genmodes.c 407

No início, o ponteiro do componente m-> é desreferenciado em um dos ramos da instrução if . Quero dizer esta expressão: m-> component-> bytesize .

Acontece ainda que esse ponteiro pode ser nulo. Isto segue da verificação: if (m-> componente) .

Este código não está necessariamente errado. É perfeitamente possível que uma ramificação não referenciada seja executada apenas se o ponteiro não for nulo. Ou seja, existe uma relação indireta entre o valor da variável m-> cl e m-> componente. Mas esse código parece muito perigoso em qualquer caso. E não há comentários explicativos.

Fragmento N8. Dupla verificação

void
pointer_and_operator::wi_fold (value_range &r, tree type,
                               const wide_int &lh_lb,
                               const wide_int &lh_ub,
                               const wide_int &rh_lb ATTRIBUTE_UNUSED,
                               const wide_int &rh_ub ATTRIBUTE_UNUSED) const
{
  // For pointer types, we are really only interested in asserting
  // whether the expression evaluates to non-NULL.
  if (wi_zero_p (type, lh_lb, lh_ub) || wi_zero_p (type, lh_lb, lh_ub))
    r = range_zero (type);
  else 
    r = value_range (type);
}

PVS-Studio Warning: V501 Existem sub-expressões idênticas 'wi_zero_p (type, lh_lb, lh_ub)' à esquerda e à direita da '||' operador. range-op.cc 2657

Algum tipo de verificação estranha. A função wi_zero_p é chamada duas vezes com o mesmo conjunto de argumentos reais. Pode-se suspeitar que, de fato, a segunda chamada deve usar os argumentos aceitos de fora: rh_lb , rh_ub. Mas não, esses argumentos estão marcados como não utilizados ( ATTRIBUTE_UNUSED ).

Portanto, não está claro para mim por que não escrever um cheque é mais simples:

if (wi_zero_p (type, lh_lb, lh_ub))
  r = range_zero (type);
else 
  r = value_range (type);

Ou há algum erro de digitação aqui? Ou um erro lógico? Eu não sei, mas o código é muito estranho.

Fragmento N9. Acesso perigoso ao array

struct algorithm
{
  struct mult_cost cost;
  short ops;
  enum alg_code op[MAX_BITS_PER_WORD];
  char log[MAX_BITS_PER_WORD];
};

static void
synth_mult (struct algorithm *alg_out, unsigned HOST_WIDE_INT t,
            const struct mult_cost *cost_limit, machine_mode mode)
{
  int m;
  struct algorithm *alg_in, *best_alg;
  ....
  /* Cache the result.  */
  if (!cache_hit)
  {
    entry_ptr->t = t;
    entry_ptr->mode = mode;
    entry_ptr->speed = speed;
    entry_ptr->alg = best_alg->op[best_alg->ops];
    entry_ptr->cost.cost = best_cost.cost;
    entry_ptr->cost.latency = best_cost.latency;
  }

  /* If we are getting a too long sequence for `struct algorithm'
     to record, make this search fail.  */
  if (best_alg->ops == MAX_BITS_PER_WORD)
    return;
  ....
}

PVS-Studio Warning: V781 O valor da variável 'best_alg-> ops' é verificado depois que foi usado. Talvez haja um erro na lógica do programa. Verifique as linhas: 3157, 3164. expmed.c 3157

Vamos encurtar o código para deixar claro o que o analisador não gosta:

if (!cache_hit)
{
  entry_ptr->alg = best_alg->op[best_alg->ops];
}
if (best_alg->ops == MAX_BITS_PER_WORD)

No início, a variável best_alg-> ops é usada para indexar a matriz. E somente então essa variável verifica se há um valor limite. Teoricamente, pode ocorrer um estouro de uma matriz (um tipo clássico de erro CWE-193: Erro de um por um ).

Isso é um erro real? E como isso está acontecendo constantemente neste artigo, não tenho certeza :). Talvez haja uma relação entre o valor desse índice e a variável cache_hit . Talvez nada seja armazenado em cache se o índice estiver no máximo ( MAX_BITS_PER_WORD ). O código da função é grande e eu não descobri.

De qualquer forma, é melhor verificar esse código. E mesmo que esteja correto, eu recomendaria acompanhar a seção considerada do programa com um comentário. Pode confundir não apenas eu ou o PVS-Studio, mas também outra pessoa.

Fragmento N10. Código que não foi corrigido

em 4 anos.No último artigo, chamei a atenção para este código:

static bool
dw_val_equal_p (dw_val_node *a, dw_val_node *b)
{
  ....
  case dw_val_class_vms_delta:
    return (!strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)
            && !strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1));
  ....
}

Aviso do PVS-Studio: V501 Existem sub-expressões idênticas '! Strcmp (a-> v.val_vms_delta.lbl1, b-> v.val_vms_delta.lbl1)' à esquerda e à direita do operador '&&'. dwarf2out.c 1481

Duas funções strcmp comparam os mesmos ponteiros. Ou seja, uma verificação claramente redundante é realizada. Em um artigo anterior, sugeri que era um erro de digitação e deveria realmente ser escrito:

return (   !strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1)
        && !strcmp (a->v.val_vms_delta.lbl2, b->v.val_vms_delta.lbl2));

No entanto, por 4 anos esse código não foi corrigido. Ao mesmo tempo, informamos os autores sobre seções suspeitas de código que descrevemos no artigo. Agora não tenho tanta certeza de que isso seja um erro. Talvez este seja apenas um código redundante. Nesse caso, a expressão pode ser simplificada:

return (!strcmp (a->v.val_vms_delta.lbl1, b->v.val_vms_delta.lbl1));

Vamos ver se os desenvolvedores do GCC mudarão esse código após um novo artigo.

Conclusão


Lembro que você pode usar esta opção de licença gratuita para verificar projetos abertos . A propósito, existem outras opções de licenciamento gratuito do PVS-Studio, inclusive mesmo para projetos fechados. Eles estão listados aqui: " Opções de licenciamento gratuitas do PVS-Studio ".

Obrigado pela atenção. E venha ler o nosso blog . Há muitas coisas interessantes.

Outros artigos sobre verificação de compiladores


  1. Verificação LLVM (Clang) (agosto de 2011), segunda verificação (agosto de 2012), terceira verificação (outubro de 2016), quarta verificação (abril de 2019)
  2. Revisão do GCC (agosto de 2016)
  3. Verifique o Huawei Ark Compiler (dezembro de 2019)
  4. Verificação da plataforma do compilador .NET ("Roslyn") (dezembro de 2015), segunda inspeção (abril de 2019)
  5. Revisão dos analisadores Roslyn (agosto de 2019)
  6. Verificando PascalABC.NET (março de 2017)



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov. Verificando o GCC 10 Compiler com PVS-Studio .

All Articles