Prueba del compilador GCC 10 con PVS-Studio

PVS-Studio vs GCC 10

El compilador GCC está escrito con abundante uso de macros. Otra comprobación del código GCC con PVS-Studio confirma una vez más la opinión de nuestro equipo de que las macros son malas. Este código es difícil de entender no solo para un analizador estático, sino también para un programador. Por supuesto, los desarrolladores de GCC ya están acostumbrados al proyecto y están bien versados ​​en él. Pero desde el lado es muy difícil entender algo. En realidad, debido a las macros, no fue posible realizar la verificación completa del código. Sin embargo, el analizador PVS-Studio, como siempre, demostró que puede encontrar errores incluso en compiladores.

Es hora de verificar el código del compilador GCC


La última vez que revisé el compilador GCC hace cuatro años. El tiempo vuela de forma rápida e imperceptible, y de alguna manera todos olvidé volver a este proyecto y volver a verificarlo. La publicación " Análisis estático en GCC 10 " hizo retroceder a esta idea .

En realidad, no es ningún secreto que los compiladores tienen sus propios analizadores de código estático integrados y también se están desarrollando. Por lo tanto, de vez en cuando escribimos artículos que el analizador estático PVS-Studio puede encontrar errores incluso dentro de los compiladores y que no estamos en vano comiendo pan :).

De hecho, no puede comparar analizadores estáticos clásicos con compiladores. Los analizadores estáticos no son solo una búsqueda de errores en el código, sino también una infraestructura desarrollada. Por ejemplo, esta es integración con sistemas como SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins, Visual Studio. Estos son mecanismos desarrollados para la supresión de advertencia masiva, que le permite comenzar a usar PVS-Studio rápidamente incluso en un proyecto antiguo grande. Esta es una lista de correo de notificaciones. Y así sucesivamente y así sucesivamente. Sin embargo, de todos modos, la primera pregunta es: "¿Puede PVS-Studio encontrar algo que los compiladores no puedan encontrar?". Por lo tanto, escribiremos artículos una y otra vez sobre cómo verificar estos compiladores.

Volvamos al tema de la verificación del proyecto GCC. No hay necesidad de detenerse en este proyecto y decir qué es. Hablemos mejor de lo que hay dentro de este proyecto.

Y dentro hay una gran cantidad de macros que interfieren con la verificación. En primer lugar, el analizador PVS-Studio genera una gran cantidad de falsos positivos. No hay nada de malo en eso, pero no es tan fácil de tomar y comenzar a estudiar el informe emitido por él. En el buen sentido, debe trabajar para suprimir las advertencias falsas en las macros. De lo contrario, las advertencias útiles simplemente se ahogan en una corriente de ruido. Esta configuración está más allá del alcance de esta tarea de escritura de artículos. En realidad, voy a ser completamente honesto: era demasiado flojo para hacer esto, aunque no hay nada complicado al respecto. Debido al ruido, ver el informe fue bastante superficial.

En segundo lugar, es muy difícil para mí, como persona no familiarizada con el proyecto, entender el código. Macros, macros ... Hay que ver qué se implementan para comprender por qué el analizador genera advertencias. Muy duro. No me gustan las macros . Alguien puede decir que sin macros en C no se puede hacer. Pero GCC no se ha escrito en C durante mucho tiempo. Sí, los archivos por razones históricas tienen la extensión .c, pero se mira allí, y allí:

//  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, esto no es C, sino C ++.

En general, las macros y el estilo de codificación hacen que sea muy difícil estudiar el informe del analizador. Así que esta vez no complaceré una larga lista de varios errores. Con dificultad y usando unas pocas tazas de café, escribí 10 fragmentos interesantes, y esto me dejó solo :).

10 fragmentos de código sospechosos


Fragmento N1, parece fracasado Copiar-Pegar

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 ();
  ....
}

Advertencia de PVS-Studio: V778 Se encontraron dos fragmentos de código similares. Quizás, este es un error tipográfico y se debe usar la variable 's2' en lugar de 's'. cfgcleanup.c 2126

De hecho, no estoy seguro de si esto es un error. Sin embargo, tengo una fuerte sospecha de que este código fue escrito usando Copy-Paste, y en el segundo bloque en un lugar olvidaron reemplazar s por s2 . Es decir, me parece que el segundo bloque de código debería ser así:

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

Fragmento N2, Typo

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

Advertencia de PVS-Studio: V519 La variable 'vr1.set' tiene valores asignados dos veces sucesivamente. Quizás esto sea un error. Verifique las líneas: 3448, 3449. tree-ssa-sccvn.c 3449

Es muy extraño que se escriban diferentes valores en la misma variable dos veces seguidas. Este es un error obvio. Al lado de este archivo está este código:

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

Lo más probable es que en el código sospechoso debería haber sido escrito exactamente igual.

Fragmento N3, Asignando una variable a sí mismo

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;
    }
  ....
}

Advertencia de PVS-Studio: V570 La variable 'ctx-> external_reduction_clauses' se asigna a sí misma. omp-low.c 935

Es muy extraño asignarse una variable a sí mismo.

Fragmento N4. 0,1,2, Freddy te recogerá.

Recientemente publiqué un artículo, " Zero, One, Two, Freddy Will Take You ". Me parece que el siguiente fragmento de código continúa la recopilación de errores discutidos en este artículo.

#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);
  ....
}

Advertencia de PVS-Studio: V560 Una parte de la expresión condicional siempre es falsa:
((machine_mode) (xop1) -> mode) == xmode1. optabs.c 1053

Tenga en cuenta estas dos subexpresiones:

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

La operación AND se realiza sobre los resultados de estas subexpresiones, lo que obviamente no tiene un significado práctico. En realidad, si la segunda subexpresión comenzó a ejecutarse, se sabe de antemano que dará como resultado falso .

Lo más probable es que haya un error tipográfico aquí en ceros y unos, y de hecho la condición debería haber sido así:

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

Por supuesto, no estoy seguro si cambié el código correctamente, ya que no estoy guiado en el proyecto.

Fragmento N5. Cambio sospechoso en el valor del 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;
}

Advertencia de PVS-Studio: el parámetro V763 'off' siempre se reescribe en el cuerpo de la función antes de usarse. ipa-polymorphic-call.c 766

El valor del argumento off se reemplaza inmediatamente por 0. Además, no hay ningún comentario explicativo. Todo esto es muy sospechoso. Algunas veces este código aparece durante la depuración. El programador necesitaba ver cómo se comportaba la función en cierto modo, por lo que cambió temporalmente el valor del argumento, y luego se olvidaron de eliminar esta línea. Como resultado, aparece un error en el código. Por supuesto, todo puede estar justo aquí, pero este código claramente necesita ser revisado y aclarado para garantizar que no surjan preguntas similares en el futuro.

Fragmento N6. Pequeña cosa

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;
  ....
}

Advertencia de PVS-Studio: V519 La variable 'new_node-> merged_comdat' recibe valores asignados dos veces sucesivamente. Quizás esto sea un error. Verifique las líneas: 406, 409. cgraphclones.c 409 La asignación se

duplica al azar. Lo más probable es que no haya nada malo. Sin embargo, siempre existe el riesgo de que en realidad se olviden de realizar alguna otra tarea.

Fragmento N7. Código que parece peligroso

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)
  ....
}

Advertencia de PVS-Studio: V595 El puntero 'm-> componente' se utilizó antes de verificarlo con nullptr. Verifique las líneas: 407, 415. genmodes.c 407

Al principio, el puntero del componente m-> se desreferencia en una de las ramas de la instrucción if . Me refiero a esta expresión: m-> componente-> bytesize .

Resulta además que este puntero puede ser nulo. Esto se deduce de la verificación: if (m-> componente) .

Este código no es necesariamente incorrecto. Es completamente posible que una rama desreferenciada solo se ejecute si el puntero no es nulo. Es decir, existe una relación indirecta entre el valor de la variable m-> cl y m-> componente. Pero este código parece muy peligroso en cualquier caso. Y no hay comentarios explicativos.

Fragmento N8. Doble verificación

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);
}

Advertencia de PVS-Studio: V501 Hay subexpresiones idénticas 'wi_zero_p (type, lh_lb, lh_ub)' a la izquierda y a la derecha de '||' operador. range-op.cc 2657

Algún tipo de verificación extraña. La función wi_zero_p se llama dos veces con el mismo conjunto de argumentos reales. Uno puede sospechar que, de hecho, la segunda llamada debe usar los argumentos aceptados desde afuera: rh_lb , rh_ub. Pero no, estos argumentos están marcados como no utilizados ( ATTRIBUTE_UNUSED ).

Por lo tanto, no me queda claro por qué no escribir un cheque es más simple:

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

¿O hay algún error tipográfico aquí? O un error lógico? No lo sé, pero el código es muy extraño.

Fragmento N9. Acceso peligroso a la matriz

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;
  ....
}

Advertencia de PVS-Studio: V781 El valor de la variable 'best_alg-> ops' se verifica después de su uso. Quizás haya un error en la lógica del programa. Verifique las líneas: 3157, 3164. expmed.c 3157 Acortemos

el código para dejar en claro lo que no le gusta al analizador:

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

Al principio, la variable best_alg-> ops se usa para indexar la matriz. Y solo entonces esta variable verifica un valor límite. Teóricamente, puede producirse un desbordamiento de una matriz (un tipo clásico de error CWE-193: Error Off-by-one ).

¿Es esto un verdadero error? Y como esto sucede constantemente en este artículo, no estoy seguro :). Quizás haya una relación entre el valor de este índice y la variable cache_hit . Quizás nada se almacena en caché si el índice está al máximo ( MAX_BITS_PER_WORD ). El código de función es grande, y no lo descubrí.

En cualquier caso, este código se verifica mejor. E incluso si resulta ser correcto, recomendaría acompañar la sección considerada del programa con un comentario. Puede confundir no solo a mí o PVS-Studio, sino también a alguien más.

Fragmento N10. Código que no se ha solucionado

en 4 años. En el último artículo, llamé la atención sobre 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));
  ....
}

Advertencia de PVS-Studio: V501 Hay subexpresiones idénticas '! Strcmp (a-> v.val_vms_delta.lbl1, b-> v.val_vms_delta.lbl1)' a la izquierda y a la derecha del operador '&&'. dwarf2out.c 1481

Dos funciones strcmp comparan los mismos punteros. Es decir, se realiza una verificación claramente redundante. En un artículo anterior, sugerí que era un error tipográfico, y en realidad debería escribirse:

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));

Sin embargo, durante 4 años este código no se ha corregido. Al mismo tiempo, informamos a los autores sobre secciones sospechosas de código que describimos en el artículo. Ahora no estoy tan seguro de que esto sea un error. Quizás esto es solo código redundante. En este caso, la expresión se puede simplificar:

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

Veamos si los desarrolladores de GCC cambiarán este código después de un nuevo artículo.

Conclusión


Le recuerdo que puede usar esta opción de licencia gratuita para verificar proyectos abiertos . Por cierto, hay otras opciones para la licencia gratuita de PVS-Studio, incluso para proyectos cerrados. Se enumeran aquí: " Opciones de licencia gratuita de PVS-Studio ".

Gracias por la atención. Y ven a leer nuestro blog . Hay muchas cosas interesantes

Nuestros otros artículos sobre la verificación de compiladores


  1. Verificación LLVM (Clang) (agosto de 2011), segunda verificación (agosto de 2012), tercera verificación (octubre de 2016), cuarta verificación (abril de 2019)
  2. Revisión del CCG (agosto de 2016)
  3. Consultar Huawei Ark Compiler (diciembre de 2019)
  4. Verificación de .NET Compiler Platform ("Roslyn") (diciembre de 2015), segunda inspección (abril de 2019)
  5. Revisión de analizadores de Roslyn (agosto de 2019)
  6. Comprobación de PascalABC.NET (marzo de 2017)



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Andrey Karpov. Comprobación del compilador GCC 10 con PVS-Studio .

All Articles