Testen des GCC 10 Compilers mit PVS-Studio

PVS-Studio gegen GCC 10

Der GCC-Compiler wurde unter Verwendung von Makros geschrieben. Eine weitere Überprüfung des GCC-Codes mit PVS-Studio bestätigt erneut die Meinung unseres Teams, dass Makros schlecht sind. Ein solcher Code ist nicht nur für einen statischen Analysator, sondern auch für einen Programmierer schwer zu verstehen. Natürlich sind GCC-Entwickler bereits an das Projekt gewöhnt und kennen sich darin gut aus. Aber von der Seite ist es sehr schwierig, etwas zu verstehen. Aufgrund der Makros war es tatsächlich nicht möglich, die Codeüberprüfung vollständig durchzuführen. Trotzdem hat der PVS-Studio-Analysator wie immer gezeigt, dass er auch in Compilern Fehler finden kann.

Zeit, den GCC-Compiler-Code zu überprüfen


Das letzte Mal, dass ich vor vier Jahren den GCC-Compiler überprüft habe. Die Zeit vergeht schnell und unmerklich, und irgendwie habe ich alle vergessen, zu diesem Projekt zurückzukehren und es erneut zu überprüfen. Die Veröffentlichung " Statische Analyse in GCC 10 " drängte auf diese Idee zurück .

Tatsächlich ist es kein Geheimnis, dass Compiler über eigene integrierte statische Code-Analysatoren verfügen und diese ebenfalls entwickeln. Daher schreiben wir von Zeit zu Zeit Artikel, dass der statische Analysator PVS-Studio selbst in Compilern Fehler finden kann und wir nicht umsonst Brot essen :).

Tatsächlich können Sie klassische statische Analysatoren nicht mit Compilern vergleichen. Statische Analysatoren sind nicht nur eine Suche nach Fehlern im Code, sondern auch eine entwickelte Infrastruktur. Dies ist beispielsweise die Integration in Systeme wie SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins und Visual Studio. Hierbei handelt es sich um entwickelte Mechanismen zur Unterdrückung von Massenwarnungen, mit denen Sie PVS-Studio auch in einem großen alten Projekt schnell einsetzen können. Dies ist eine Benachrichtigungs-Mailingliste. Und so weiter und so fort. Die erste Frage lautet jedoch: "Kann PVS-Studio etwas finden, das Compiler nicht finden können?". Wir werden also immer wieder Artikel über die Überprüfung dieser Compiler selbst schreiben.

Kommen wir zurück zum Thema der Überprüfung des GCC-Projekts. Es ist nicht nötig, sich mit diesem Projekt zu befassen und zu sagen, was es ist. Sprechen wir besser darüber, was in diesem Projekt enthalten ist.

Und im Inneren gibt es eine große Anzahl von Makros, die die Überprüfung stören. Erstens erzeugt der PVS-Studio-Analysator eine große Anzahl von Fehlalarmen. Daran ist nichts auszusetzen, aber es ist nicht so einfach, den von ihm herausgegebenen Bericht zu studieren. In guter Weise müssen Sie arbeiten, um falsche Warnungen in Makros zu unterdrücken. Ansonsten ertrinken nützliche Warnungen nur in einem Geräuschstrom. Dieses Setup geht über den Rahmen dieser Artikelschreibaufgabe hinaus. Eigentlich werde ich ganz ehrlich sein - ich war einfach zu faul, um das zu tun, obwohl es nichts Kompliziertes gibt. Aufgrund des Rauschens war das Anzeigen des Berichts recht oberflächlich.

Zweitens ist es für mich als Person, die mit dem Projekt nicht vertraut ist, sehr schwierig, den Code zu verstehen. Makros, Makros ... Sie müssen sich ansehen, was sie bereitgestellt werden, um zu verstehen, warum der Analysator Warnungen generiert. Sehr schwer. Ich mag keine Makros . Jemand kann sagen, dass ohne Makros in C nicht geht. Aber GCC wurde schon lange nicht mehr in C geschrieben. Ja, Dateien aus historischen Gründen haben die Erweiterung .c, aber Sie sehen dort und dort:

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

Dies ist eindeutig nicht C, sondern C ++.

Im Allgemeinen machen es Makros und Codierungsstile sehr schwierig, den Analysatorbericht zu studieren. Diesmal werde ich also nicht auf eine lange Liste verschiedener Fehler eingehen. Mit Mühe und mit ein paar Tassen Kaffee schrieb ich 10 interessante Fragmente aus, und das ließ mich in Ruhe :).

10 verdächtige Codefragmente


Fragment N1, scheint erfolglos zu kopieren und einzufügen

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 Warnung: V778 Es wurden zwei ähnliche Codefragmente gefunden. Möglicherweise ist dies ein Tippfehler und die Variable 's2' sollte anstelle von 's' verwendet werden. cfgcleanup.c 2126

Tatsächlich bin ich mir nicht sicher, ob dies ein Fehler ist. Ich habe jedoch den starken Verdacht, dass dieser Code mit Copy-Paste geschrieben wurde, und im zweiten Block an einer Stelle haben sie vergessen, s durch s2 zu ersetzen . Das heißt, es scheint mir, dass der zweite Codeblock so sein sollte:

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

Fragment N2, Tippfehler

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

PVS-Studio Warnung: V519 Der Variablen 'vr1.set' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 3448, 3449. tree-ssa-sccvn.c 3449

Es ist sehr seltsam, dass unterschiedliche Werte zweimal hintereinander in dieselbe Variable geschrieben werden. Dies ist ein offensichtlicher Tippfehler. Neben dieser Datei befindet sich der folgende Code:

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

Höchstwahrscheinlich sollte der verdächtige Code genau gleich geschrieben worden sein.

Fragment N3, sich selbst eine Variable zuweisen

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

PVS-Studio-Warnung: V570 Die Variable 'ctx-> Outer_reduction_clauses' wird sich selbst zugewiesen. omp-low.c 935

Es ist sehr seltsam, sich selbst eine Variable zuzuweisen.

Fragment N4. 0,1,2, Freddy holt dich ab.

Ich habe kürzlich einen Artikel veröffentlicht: " Null, Eins, Zwei, Freddy wird dich nehmen ." Es scheint mir, dass das folgende Codefragment die in diesem Artikel beschriebene Sammlung von Fehlern fortsetzt.

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

PVS-Studio-Warnung: V560 Ein Teil des bedingten Ausdrucks ist immer falsch:
((machine_mode) (xop1) -> mode) == xmode1. optabs.c 1053

Beachten Sie diese beiden Unterausdrücke:

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

Die UND-Operation wird an den Ergebnissen dieser Unterausdrücke durchgeführt, was offensichtlich keine praktische Bedeutung hat. Wenn der zweite Unterausdruck ausgeführt wird, ist im Voraus bekannt, dass er zu false führt .

Höchstwahrscheinlich gibt es hier einen Tippfehler in Nullen und Einsen, und tatsächlich hätte die Bedingung so sein müssen:

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

Natürlich bin ich mir nicht sicher, ob ich den Code richtig geändert habe, da ich nicht im Projekt geführt werde.

Fragment N5. Verdächtige Änderung des Argumentwerts

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 Warnung: V763 Der Parameter 'off' wird vor der Verwendung immer im Funktionskörper neu geschrieben. ipa-polymorphic-call.c 766

Der Wert des Arguments off wird sofort durch 0 ersetzt. Außerdem gibt es keinen erklärenden Kommentar. Das alles ist sehr verdächtig. Manchmal wird dieser Code beim Debuggen angezeigt. Der Programmierer musste sehen, wie sich die Funktion in einem bestimmten Modus verhält, also änderte er vorübergehend den Wert des Arguments und vergaß dann, diese Zeile zu löschen. Infolgedessen wird im Code ein Fehler angezeigt. Natürlich mag hier alles stimmen, aber dieser Code muss eindeutig überprüft und präzisiert werden, um sicherzustellen, dass ähnliche Fragen in Zukunft nicht mehr auftauchen.

Fragment N6. Kleines Ding

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

PVS-Studio-Warnung: V519 Der Variablen 'new_node-> fusioned_comdat' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Kontrollzeilen: 406, 409. cgraphclones.c 409 Die Zuordnung wird

zufällig dupliziert. Höchstwahrscheinlich nichts falsch. Es besteht jedoch immer das Risiko, dass sie in Wirklichkeit vergessen haben, eine andere Aufgabe auszuführen.

Fragment N7. Code, der gefährlich aussieht

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

PVS-Studio Warnung: V595 Der Zeiger 'm-> component' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 407, 415. genmodes.c 407

Zu Beginn wird der Zeiger m-> component in einem der Zweige der if-Anweisung dereferenziert . Ich meine diesen Ausdruck: m-> Komponente-> Bytesize .

Es stellt sich ferner heraus, dass dieser Zeiger null sein kann. Dies folgt aus der Prüfung: if (m-> Komponente) .

Dieser Code ist nicht unbedingt falsch. Es ist durchaus möglich, dass ein dereferenzierter Zweig nur ausgeführt wird, wenn der Zeiger nicht null ist. Das heißt, es besteht eine indirekte Beziehung zwischen dem Wert der Variablen m-> cl und m-> Komponente. Aber dieser Code sieht auf jeden Fall sehr gefährlich aus. Und es gibt keine erklärenden Kommentare.

Fragment N8. Überprüfen

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 Warnung: V501 Links und rechts vom '||' befinden sich identische Unterausdrücke 'wi_zero_p (Typ, lh_lb, lh_ub)'. Operator. range-op.cc 2657 Eine

Art seltsamer Scheck. Die Funktion wi_zero_p wird zweimal mit denselben tatsächlichen Argumenten aufgerufen. Man kann vermuten, dass der zweite Aufruf tatsächlich die von außen akzeptierten Argumente verwenden sollte: rh_lb , rh_ub. Nein, diese Argumente werden als nicht verwendet markiert ( ATTRIBUTE_UNUSED ).

Daher ist mir nicht klar, warum es einfacher ist, keinen Scheck auszustellen:

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

Oder gibt es hier einen Tippfehler? Oder ein logischer Fehler? Ich weiß es nicht, aber der Code ist sehr seltsam.

Fragment N9. Gefährlicher Array-Zugriff

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 Warnung: V781 Der Wert der Variablen 'best_alg-> ops' wird nach ihrer Verwendung überprüft. Möglicherweise liegt ein Fehler in der Programmlogik vor. Überprüfen Sie die Zeilen: 3157, 3164. expmed.c 3157 Kürzen

wir den Code, um zu verdeutlichen, was dem Analysator nicht gefällt:

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

Zu Beginn wird die Variable best_alg-> ops verwendet, um das Array zu indizieren. Und erst dann prüft diese Variable auf einen Grenzwert. Theoretisch kann ein Überlauf eines Arrays auftreten (eine klassische Art von Fehler CWE-193: Off-by-One-Fehler ).

Ist das ein echter Fehler? Und da dies in diesem Artikel ständig passiert, bin ich mir nicht sicher :). Möglicherweise besteht eine Beziehung zwischen dem Wert dieses Index und der Variablen cache_hit . Möglicherweise wird nichts zwischengespeichert, wenn der Index maximal ist ( MAX_BITS_PER_WORD ). Der Funktionscode ist groß und ich habe es nicht herausgefunden.

In jedem Fall wird dieser Code am besten überprüft. Und selbst wenn es sich als richtig herausstellt, würde ich empfehlen, dem betrachteten Programmabschnitt einen Kommentar beizufügen. Es kann nicht nur mich oder PVS-Studio verwirren, sondern auch jemand anderen.

Fragment N10. Code, der seit 4 Jahren nicht mehr repariert

wurde . Im letzten Artikel habe ich auf diesen Code aufmerksam gemacht:

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

PVS-Studio-Warnung: V501 Links und rechts vom Operator '&&' befinden sich identische Unterausdrücke '! Strcmp (a-> v.val_vms_delta.lbl1, b-> v.val_vms_delta.lbl1)'. dwarf2out.c 1481

Zwei strcmp- Funktionen vergleichen dieselben Zeiger. Das heißt, eine eindeutig redundante Prüfung wird durchgeführt. In einem früheren Artikel habe ich vorgeschlagen, dass es sich um einen Tippfehler handelt und eigentlich geschrieben werden sollte:

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

Dieser Code wurde jedoch seit 4 Jahren nicht mehr behoben. Gleichzeitig haben wir die Autoren über verdächtige Codeabschnitte informiert, die wir im Artikel beschrieben haben. Jetzt bin ich mir nicht so sicher, ob das ein Fehler ist. Vielleicht ist dies nur redundanter Code. In diesem Fall kann der Ausdruck vereinfacht werden:

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

Mal sehen, ob die GCC-Entwickler diesen Code nach einem neuen Artikel ändern werden.

Fazit


Ich erinnere Sie daran, dass Sie diese kostenlose Lizenzoption verwenden können , um offene Projekte zu überprüfen . Übrigens gibt es andere Möglichkeiten für die kostenlose Lizenzierung von PVS-Studio, auch für geschlossene Projekte. Sie sind hier aufgelistet: " PVS-Studio Free Licensing Options ".

Vielen Dank für Ihre Aufmerksamkeit. Und lesen Sie unseren Blog . Es gibt viele interessante Dinge.

Unsere anderen Artikel zum Überprüfen von Compilern


  1. LLVM-Prüfung (Clang) (August 2011), zweite Prüfung (August 2012), dritte Prüfung (Oktober 2016), vierte Prüfung (April 2019)
  2. GCC Review (August 2016)
  3. Überprüfen Sie Huawei Ark Compiler (Dezember 2019)
  4. Überprüfung der .NET Compiler-Plattform („Roslyn“) (Dezember 2015), zweite Inspektion (April 2019)
  5. Roslyn Analyzers Review (August 2019)
  6. Überprüfen von PascalABC.NET (März 2017)



Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov. Überprüfen des GCC 10 Compilers mit PVS-Studio .

All Articles