Analysieren des GTK+ Cross-Platform Widget Toolkit mit PVS-Studio

Analysieren des GTK+ Cross-Platform Widget Toolkit mit PVS-Studio

Widget-Toolkits werden verwendet, um den Prozess der Anwendungs-GUI-Entwicklung zu vereinfachen, und GTK+ ist eines davon. Dieses Projekt habe ich für meinen ersten Artikel über den PVS-Studio-Analyzer ausgewählt. Ich habe den Code von GTK+ mit PVS-Studio auf mögliche Fehler gescannt und ziemlich viele Meldungen über Fehler und verdächtige Fragmente bekommen. Einige von ihnen sind ziemlich kritisch. Die Gesamtzahl der Fehler ist zu groß für einen Artikel, daher werde ich nur über einige davon sprechen, die die typischsten sind.

Einführung

GTK+ (Abkürzung für GIMP ToolKit) ist ein plattformübergreifendes Widget-Toolkit zum Erstellen grafischer Benutzeroberflächen. Es ist unter den Bedingungen der LGPL lizenziert, sodass sowohl freie als auch proprietäre Software es verwenden kann. Es ist neben Qt eines der beliebtesten Toolkits für die Windowing-Systeme Wayland und X11.

Wir haben den Code des Toolkits mit dem statischen Analysator PVS-Studio, Version 6.02, gescannt und die Diagnosemeldungen untersucht.

Redundanter Code

Lassen Sie uns zunächst Warnungen besprechen, die sich mit der Bildung logischer Ausdrücke befassen. Solche Probleme sind nicht immer Fehler; es handelt sich lediglich um zusätzliche Überprüfungen, die das Lesen und Verstehen von Bedingungen erschweren. Ausdrücke mit solchen Prüfungen können stark vereinfacht werden.

V728 Eine übermäßige Prüfung kann vereinfacht werden. Das '||' Der Operator ist von den entgegengesetzten Ausdrücken '!mount' und 'mount' umgeben. gtkplacesview.c 708

static void
add_volume (....)
{
  ....
  GMount *mount;
  ....
  if (!mount ||
      (mount && !g_mount_is_shadowed (mount)))
  ....
}

Dieser Code enthält eine zusätzliche Überprüfung des 'mount'-Zeigers und kann wie folgt modifiziert werden:

  if (!mount || !g_mount_is_shadowed (mount)))

Ein weiterer ähnlicher Fall:

V728 Eine übermäßige Prüfung kann vereinfacht werden. Das '||' Der Operator ist von entgegengesetzten Ausdrücken 'ret' und '!ret' umgeben. gtktreeview.c 13682

void
gtk_tree_view_get_cell_area (....)
{
  ....
  gboolean ret = ...;
  ....
      /* Get vertical coords */
      if ((!ret && tree == NULL) || ret)
  ....
}

Noch eine überflüssige Prüfung; Diesmal ist es die boolesche Variable 'ret'. Vereinfachen wir den Code:

if (ret || tree == NULL)

V590 Erwägen Sie die Überprüfung von 'str[0] =='\0' || str[0] !='U'-Ausdruck. Der Ausdruck ist übertrieben oder enthält einen Druckfehler. gtkcomposetable.c 62

static gboolean
is_codepoint (const gchar *str)
{
  int i;

  /* 'U' is not code point but 'U00C0' is code point */
  if (str[0] == '\0' || str[0] != 'U' || str[1] == '\0')
    return FALSE;

  for (i = 1; str[i] != '\0'; i++)
    {
      if (!g_ascii_isxdigit (str[i]))
        return FALSE;
    }

  return TRUE;
}

Die Prüfung str[0] =='\0' ist redundant, da es sich um einen Sonderfall des Ausdrucks str[0] !='U' handelt. Wir können den Code vereinfachen, indem wir das zusätzliche Häkchen entfernen:

if (str[0] != 'U' || str[1] == '\0')
    return FALSE;

All diese Probleme sind nicht wirklich Fehler. Der Code wird erfolgreich ausgeführt; Es enthält nur einige unnötige Prüfungen, die ebenfalls ausgeführt werden.

Codewiederverwendung

Die Softwareentwicklungsbranche ist stark auf die Wiederverwendung von Code angewiesen. In der Tat, warum das Rad neu erfinden? Eine sehr häufige Fehlerquelle ist die Copy-Paste-Technik, bei der Codeblöcke kopiert und dann leicht bearbeitet werden. Programmierer neigen dazu, solche Blöcke zu überspringen und zu vergessen, sie zu beheben, was zu Fehlern führt. Eine der Stärken von PVS-Studio ist die Fähigkeit, solche Fragmente zu erkennen.

Hier sind einige Beispiele für Fehler, die durch den Missbrauch von Kopieren und Einfügen verursacht wurden:

V523 Die 'then'-Anweisung entspricht der 'else'-Anweisung. gtkprogressbar.c 1232

static void
gtk_progress_bar_act_mode_enter (GtkProgressBar *pbar)
{
  ....
  /* calculate start pos */
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      if (!inverted)
        {
          priv->activity_pos = 0.0;
          priv->activity_dir = 0;
        }
      else
        {
          priv->activity_pos = 1.0;
          priv->activity_dir = 1;
        }
    }
  else
    {
      if (!inverted)
        {
          priv->activity_pos = 0.0;
          priv->activity_dir = 0;
        }
      else
        {
          priv->activity_pos = 1.0;
          priv->activity_dir = 1;
        }
    }
  ....
}

Der Block der 'if (orientation ==GTK_ORIENTATION_HORIZONTAL)'-Anweisung und der entsprechende Else-Block enthalten denselben Code. Es kann entweder eine unvollständige Funktionalität oder ein Fehler sein.

V501 Es gibt identische Teilausdrücke '(box->corner[GTK_CSS_TOP_RIGHT].horizontal)' links und rechts vom Operator '>'. gtkcssshadowvalue.c 685

V501 Es gibt identische Teilausdrücke '(box->corner[GTK_CSS_TOP_LEFT].horizontal)' links und rechts vom Operator '>'. gtkcssshadowvalue.c 696

static void
draw_shadow_corner (....
                    GtkRoundedBox       *box,
                                         ....)
{
  ....
  overlapped = FALSE;
  if (corner == GTK_CSS_TOP_LEFT || 
      corner == GTK_CSS_BOTTOM_LEFT)
    {
      ....
      max_other = MAX(box->corner[GTK_CSS_TOP_RIGHT].horizontal,
                      box->corner[GTK_CSS_TOP_RIGHT].horizontal);
      ....
    }
  else
    {
      ....
      max_other = MAX(box->corner[GTK_CSS_TOP_LEFT].horizontal
                      box->corner[GTK_CSS_TOP_LEFT].horizontal);
      ....
    }
  ....
}

Das MAX-Makro erhält identische Variablen als Argumente. Vielleicht hat der Programmierer vergessen, 'GTK_CSS_TOP_RIGHT' und 'GTK_CSS_TOP_LEFT' durch die entsprechenden konstanten Werte zu ersetzen; oder vielleicht hätte der Vergleich eine ganz andere Variable beinhalten sollen.

V501 Es gibt identische Unterausdrücke 'G_PARAM_EXPLICIT_NOTIFY' links und rechts vom '|' Operator. gtkcalendar.c 400

static void
gtk_calendar_class_init (GtkCalendarClass *class)
{
  ....
  g_object_class_install_property (gobject_class,
    PROP_YEAR,
    g_param_spec_int ("year",
      P_("Year"),
      P_("The selected year"),
      0, G_MAXINT >> 9, 0,
      GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY|
                          G_PARAM_EXPLICIT_NOTIFY));
  ....
}

In diesem Code wurde entweder die Konstante 'G_PARAM_EXPLICIT_NOTIFY' ein weiteres Mal kopiert oder der Programmierer hat vergessen, sie durch eine andere Konstante zu ersetzen.

Ein weiterer ähnlicher Fall, der sich mit der Konstante 'G_PARAM_DEPRECATED' befasst:

V501 Es gibt identische Unterausdrücke 'G_PARAM_DEPRECATED' links und rechts vom '|' Operator. gtkmenubar.c 275

static void
gtk_menu_bar_class_init (GtkMenuBarClass *class)
{
  ....
  gtk_widget_class_install_style_property (widget_class,
    g_param_spec_int ("internal-padding",
      P_("Internal padding"),
      P_("Amount of border space between ...."),
      0, G_MAXINT, 0,
      GTK_PARAM_READABLE |
      G_PARAM_DEPRECATED|G_PARAM_DEPRECATED));
  ....
}

Fehler im Zusammenhang mit Copy-Paste lauern oft in langen Initialisierungslisten. Sie sind für einen Menschen schwer zu erkennen, und hier kann Ihnen ein statischer Analysator helfen.

Das folgende Beispiel enthält eine sehr lange Initialisierungsliste, daher ist es nicht verwunderlich, dass darin ein Fehler enthalten ist:

V519 Die Variable 'impl_class->set_functions' wird zweimal hintereinander mit Werten belegt. Vielleicht ist dies ein Fehler. Überprüfen Sie die Zeilen:5760, 5761. gdkwindow-x11.c 5761

static void
gdk_window_impl_x11_class_init (GdkWindowImplX11Class *klass)
{
  ....
  GdkWindowImplClass *impl_class = GDK_WINDOW_IMPL_CLASS (klass);
  ....
  impl_class->set_decorations = gdk_x11_window_set_decorations;
  impl_class->get_decorations = gdk_x11_window_get_decorations;
  impl_class->set_functions = gdk_x11_window_set_functions;
  impl_class->set_functions = gdk_x11_window_set_functions;
  ....
}

Zuerst dachte ich, dass es ein fehlendes 'get-set'-Paar gibt, wie im vorherigen Fall:'set_functions' und 'get_functions'. Aber es stellte sich heraus, dass die 'GdkWindowImplClass'-Struktur kein 'get_functions'-Feld hatte. Vielleicht hat der Programmierer aus Versehen eine zusätzliche Kopie der Initialisierungszeile erstellt, oder vielleicht wollte er sie durch einen anderen Code ersetzen, hat aber alles darüber vergessen. Wie auch immer, sie müssen sicherstellen, dass sie alles initialisieren, was initialisiert werden sollte, und gegebenenfalls die zusätzliche Anweisung entfernen.

Die nächste Warnung ähnelt der vorherigen:

V519 Die Variable 'impl_class->set_functions' wird zweimal hintereinander mit Werten belegt. Vielleicht ist dies ein Fehler. Überprüfen Sie die Zeilen:1613, 1614. gdkwindow-broadway.c 1614

static void
gdk_window_impl_broadway_class_init 
  (GdkWindowImplBroadwayClass *klass)
{
  ....
  GdkWindowImplClass *impl_class = GDK_WINDOW_IMPL_CLASS (klass);
  ....
  impl_class->set_functions = 
    gdk_broadway_window_set_functions;
  impl_class->set_functions = 
    gdk_broadway_window_set_functions;
  ....
}

Auch hier haben wir es mit der doppelten Zuweisung 'impl_class->set_functions' zu tun. Es kann aus dem vorherigen Beispiel migriert worden sein.

Manchmal werden ähnlich aussehende Funktionen vollständig kopiert und Programmierer vergessen, ihre Körper zu modifizieren. Helfen wir diesen vergesslichen Programmierern und beheben die gefundenen Fehler:

V524 Es ist seltsam, dass der Hauptteil der Funktion „gtk_mirror_bin_get_preferred_height“ vollständig dem Hauptteil der Funktion „gtk_mirror_bin_get_preferred_width“ entspricht. offscreen_window2.c 340

static void
gtk_mirror_bin_get_preferred_width (GtkWidget *widget,
                                    gint      *minimum,
                                    gint      *natural)
{
  GtkRequisition requisition;
  gtk_mirror_bin_size_request (widget, &requisition);
  *minimum = *natural = requisition.width;
}

static void
gtk_mirror_bin_get_preferred_height (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
{
  GtkRequisition requisition;

  gtk_mirror_bin_size_request (widget, &requisition);

  *minimum = *natural = requisition.width;
}

In der Funktion gtk_mirror_bin_get_preferred_height muss wahrscheinlich 'requisition.height' anstelle von 'requisition.width' verwendet werden. Dann sollte es so aussehen:

 *minimum = *natural = requisition.height;

Vielleicht war es ursprünglich genau so konzipiert und es gibt keinen Fehler, aber dieser Code sieht seltsam aus.

Hier ist ein weiteres Beispiel, wo, glaube ich, auch Breite und Länge verwechselt werden:

V524 Es ist merkwürdig, dass der Hauptteil der Funktion „gtk_hsv_get_preferred_height“ vollständig dem Hauptteil der Funktion „gtk_hsv_get_preferred_width“ entspricht. gtkhsv.c 310

static void
gtk_hsv_get_preferred_width (GtkWidget *widget,
                             gint      *minimum,
                             gint      *natural)
{
  GtkHSV *hsv = GTK_HSV (widget);
  GtkHSVPrivate *priv = hsv->priv;
  gint focus_width;
  gint focus_pad;

  gtk_widget_style_get (widget,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        NULL);

  *minimum = priv->size + 2 * (focus_width + focus_pad);
  *natural = priv->size + 2 * (focus_width + focus_pad);
}

static void
gtk_hsv_get_preferred_height (GtkWidget *widget,
                              gint      *minimum,
                              gint      *natural)
{
  GtkHSV *hsv = GTK_HSV (widget);
  GtkHSVPrivate *priv = hsv->priv;
  gint focus_width;
  gint focus_pad;

  gtk_widget_style_get (widget,
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_pad,
                        NULL);

  *minimum = priv->size + 2 * (focus_width + focus_pad);
  *natural = priv->size + 2 * (focus_width + focus_pad);
}

Da sowohl die Höhe als auch die Breite auf die gleiche Weise berechnet werden, ist es wahrscheinlich besser, eine Funktion anstelle von zwei zu verwenden. Aber wenn diese Ausdrücke anders sein sollten, sollte man daran denken, die notwendigen Änderungen vorzunehmen.

Im nächsten Beispiel ist nicht ganz klar, welches Argument anstelle des kopierten Arguments 'Komponentenname' verwendet werden muss, aber es ist offensichtlich seltsam, einen String mit sich selbst zu vergleichen:

V549 Das erste Argument der Funktion 'strcmp' ist gleich dem zweiten Argument. gtkrc.c 1400

GtkStyle *
gtk_rc_get_style_by_paths (....)
{
  ....
  pos = gtk_widget_path_append_type (path, component_type);
  if (component_name != NULL && 
      strcmp (component_name, component_name) != 0)    // <=
    gtk_widget_path_iter_set_name (path, pos, component_name);
  ....
}

Weiter geht es mit Warnungen zum Kopieren von Code:

V570 Die Variable 'tmp_info' ist sich selbst zugewiesen. gtkimcontextxim.c 442

static GtkXIMInfo *
get_im (....)
{
  ....
  GtkXIMInfo *info;
  ....
  info = NULL;
  tmp_list = open_ims;
  while (tmp_list)
    {
      ....
      else
        {
          tmp_info = tmp_info;           // <=
          break;
        }
      ....
    }
  if (info == NULL)
    {
      ....
    }
  ....
}

Nach der Untersuchung dieses Codes zieht man den logischen Schluss, dass der Programmierer eigentlich der ‚info‘-Variablen einen Wert zuweisen wollte:Nur dann würde der Code nach ‚while‘ Sinn machen. Versuchen wir es zu beheben:

info = tmp_info;

Wir sind mit unserer Erörterung von Fehlern im Zusammenhang mit dem Kopieren von Code hier fertig. Eine Schlussfolgerung aus dem oben Gesagten ist, dass es sich um ein sehr häufiges Fehlermuster handelt, das lange Zeit verborgen bleiben kann. Diese Fehler sind im Allgemeinen schwer zu finden, da sie beim Durchsuchen des Codes nicht ins Auge fallen.

Zeigerbehandlung

Die nächste Kategorie möglicher Fehler befasst sich mit der falschen Verwendung von Zeigern. Unvorsichtiger Umgang mit Zeigern kann zu Abstürzen oder undefiniertem Verhalten führen.

V528 Es ist seltsam, dass der Zeiger auf den Typ 'char' mit dem Wert '\0' verglichen wird. Wahrscheinlich gemeint:*data->groups[0] !='\0'. gtkrecentmanager.c 979

struct _GtkRecentData
{
  ....
  gchar **groups;
  ....
};

gboolean
gtk_recent_manager_add_full (GtkRecentManager    *manager,
                             const gchar         *uri,
                             const GtkRecentData *data)
{
  ....
  if (data->groups && data->groups[0] != '\0')
      ....
  ....
}

Was unter der Adresse 'data->groups[0]' zu finden ist, ist 'gchar*', also ebenfalls ein Zeiger, der nicht mit '\0' zu vergleichen ist. In diesem Beispiel wird der Zeiger 'data->groups[0]' tatsächlich mit einem Nullzeiger verglichen. Wenn der Programmierer wirklich sicherstellen musste, dass der Zeiger nicht null ist, dann ist die richtige Vorgehensweise die folgende:


if (data->groups && data->groups[0] != NULL)

Und wenn sie das an der Adresse 'data->groups[0]' gefundene Zeichen darauf testen wollten, ob es ein Null-Terminator ist, dann hätte der Zeiger dereferenziert werden sollen:

if (data->groups && *data->groups[0] != '\0')

Hier ist ein weiteres ähnliches Beispiel, das ebenfalls einen fehlerhaften Vergleich behandelt:

V528 Es ist seltsam, dass der Zeiger auf den Typ 'char' mit dem Wert '\0' verglichen wird. Wahrscheinlich gemeint:*priv->icon_list[0] =='\0'. gtkscalebutton.c 987

struct _GtkScaleButtonPrivate
{
  ....
  gchar **icon_list;
  ....
};

struct _GtkScaleButton
{
  ....
  GtkScaleButtonPrivate *priv;
};

static void
gtk_scale_button_update_icon (GtkScaleButton *button)
{
  GtkScaleButtonPrivate *priv = button->priv;
  ....
  if (!priv->icon_list || priv->icon_list[0] == '\0')
  ....
}

Bei der Verwendung von Zeigern in C/C++ muss man vorsichtig sein. Wenn Sie nicht sicher sind, ob der Zeiger wirklich auf irgendwelche Daten zeigt, müssen Sie ihn auf null testen.

Der Zugriff auf einen Speicherblock durch einen Nullzeiger führt zu undefiniertem Verhalten oder einem Absturz. Die folgenden Diagnosemeldungen warnen Sie, wenn solch ein gefährlicher Zugriff auftreten kann.

V595 Der „Completion“-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:2231, 2239. gtkentrycompletion.c 2231

static gboolean
gtk_entry_completion_key_press (...., gpointer user_data)
{
  ....
  GtkEntryCompletion *completion = 
    GTK_ENTRY_COMPLETION (user_data);

  if (!completion->priv->popup_completion)
    return FALSE;

  ....
  if (completion && completion->priv->completion_timeout) // <=
    {
      ....
    }
  ....
}

Im Funktionsrumpf testet der Programmierer den 'Vervollständigungs'-Zeiger auf null und verwendet ihn dann:

if (completion && completion->priv->completion_timeout)

Diese Prüfung zeigt die Annahme des Programmierers an, dass der Zeiger null sein kann. Jedoch wurde früher im Code auf diesen Zeiger ohne eine solche Prüfung zugegriffen:

if (!completion->priv->popup_completion)
    return FALSE;

Wenn der Zeiger hier null zu sein scheint, erhalten wir undefiniertes Verhalten. Entweder fehlt die Prüfung des 'Completion'-Zeigers früher im Code oder die spätere Prüfung macht keinen Sinn und ist nicht notwendig.

Es gibt über ein Dutzend solcher Fälle im Code des Toolkits, also besprechen wir nur ein weiteres Beispiel:

V595 Der 'Dispatch->Backend'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:1570, 1580. gtkprintbackendcups.c 1570

static void 
cups_dispatch_watch_finalize (GSource *source)
{
  ....
  if (dispatch->backend->username != NULL)
    username = dispatch->backend->username;
  else
    username = cupsUser ();
  ....
  if (dispatch->backend)
    dispatch->backend->authentication_lock = FALSE;
  ....
}

Der 'Dispatch->Backend'-Zeiger wird erst überprüft, nachdem darauf zugegriffen wurde, daher ist dieser Code potenziell unsicher.

Nachfolgend finden Sie eine Liste anderer ähnlicher Probleme. Es enthält keine Warnungen bei Zeigerüberprüfungen in Makros. Obwohl diese Makros auch Probleme mit der Verwendung von Nullzeigern haben, ist es auch möglich, dass der Programmierer einfach Makros genommen hat, die zu ihnen passten, zusammen mit den Prüfungen, die sie nicht brauchten.

V595 Der 'impl->toplevel'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:514, 524. gdkwindow-x11.c 514

V595 Der Zeiger 'pointer_info' wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:9610, 9638. gdkwindow.c 9610

V595 Der 'elt'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:2218, 2225. gtktreemodelfilter.c 2218

V595 Der 'tmp_list'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:5817, 5831. gtktreeview.c 5817

V595 Der 'dispatch->data_poll'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen:1470, 1474. gtkprintbackendcups.c 1470

Andere Fehler

Abschließend besprechen wir eine Gruppe verschiedener Warnungen bezüglich möglicher algorithmischer Fehler oder Tippfehler.

Im folgenden Beispiel hat der Autor vergessen, 'break'-Anweisungen am Ende von 'case'-Anweisungen zu schreiben:

V519 Die Variable 'type' wird zweimal hintereinander mit Werten belegt. Vielleicht ist dies ein Fehler. Überprüfen Sie die Zeilen:187, 189. testselection.c 189

void
selection_get (....
               guint      info,
               ....)
{
  ....
  switch (info)
    {
    case COMPOUND_TEXT:
    case TEXT:
      type = seltypes[COMPOUND_TEXT];
    case STRING:
      type = seltypes[STRING];
    }
  ....
}

Unabhängig davon, welcher der drei Werte 'info' zugewiesen wird, erhalten wir am Ende den 'type =seltypes[STRING];' Abtretung. Um dies zu vermeiden, müssen wir die 'break'-Anweisung hinzufügen:


switch (info)
    {
    case COMPOUND_TEXT:
    case TEXT:
      type = seltypes[COMPOUND_TEXT];
      break;
    case STRING:
      type = seltypes[STRING];
      break;
    }

Das nächste Fragment ist sehr verdächtig:Eine Variable ('i') wird als Zähler sowohl für die äußere als auch für die innere Schleife verwendet:

V535 Die Variable 'i' wird für diese Schleife und für die äußere Schleife verwendet. Überprüfen Sie die Zeilen:895, 936. gtkstyleproperties.c 936

void
gtk_style_properties_merge (....)
{
  ....
  guint i;
  ....
  for (i = 0; i < prop_to_merge->values->len; i++)
    {
     ....
      else if (_gtk_is_css_typed_value_of_type (data->value, 
                G_TYPE_PTR_ARRAY) && value->value != NULL)
        {
          ....
          for (i = 0; i < array_to_merge->len; i++)
            g_ptr_array_add (array, 
              g_ptr_array_index (array_to_merge, i));
        }
    ....
    }
  ....
}

Ich bin mir nicht sicher, auf welchen Wert sich die Variable „i“ bezieht, nachdem die innere Schleife ausgeführt wurde, und wie die äußere Schleife danach ausgeführt wird. Um diese Unsicherheit zu vermeiden, sollte die innere Schleife einen eigenen Zähler verwenden, zum Beispiel:

guint j;
for (j = 0; j < array_to_merge->len; j++)
  g_ptr_array_add (array, 
  g_ptr_array_index (array_to_merge, j));

Zwei weitere Fälle potenziell unsicherer Verwendung von Schleifenzählern:

V557 Array-Überlauf ist möglich. Der Wert des Index „i + 1“ könnte 21 erreichen. gtkcssselector.c 1219

V557 Array-Überlauf ist möglich. Der Wert des Index „i + 1“ könnte 21 erreichen. gtkcssselector.c 1224

#define G_N_ELEMENTS(arr)   (sizeof (arr) / sizeof ((arr)[0]))

static GtkCssSelector *
parse_selector_pseudo_class (....)
{
  static const struct {
    ....
  } pseudo_classes[] = {
    { "first-child",   0, 0,  POSITION_FORWARD,  0, 1 },
    ....
    { "drop(active)",  0, GTK_STATE_FLAG_DROP_ACTIVE, }
  };
  guint i;
  ....
  for (i = 0; i < G_N_ELEMENTS (pseudo_classes); i++)
    {
      ....
      {
        if (pseudo_classes[i + 1].state_flag == 
            pseudo_classes[i].state_flag)
          _gtk_css_parser_error_full (parser,
          GTK_CSS_PROVIDER_ERROR_DEPRECATED,
          "The :%s pseudo-class is deprecated. Use :%s instead.",
          pseudo_classes[i].name,
          pseudo_classes[i + 1].name);
        ....
      }
       ....
    }
  ....
}

Die Schleife basiert auf der Anzahl der Elemente im Array „pseudo_classes“, und ich hoffe, dass sie niemals das letzte Element erreicht. Andernfalls führt das Konstrukt 'pseudo_classes[i+1]' zu einer Indizierung außerhalb der Grenzen des Arrays.

Der nächste mögliche Fehler sieht aus wie ein Tippfehler:

V559 Verdächtige Zuweisung innerhalb des Bedingungsausdrucks des 'if'-Operators. gdkselection-x11.c 741

gboolean
gdk_x11_display_utf8_to_compound_text (....)
{
  ....
  GError *error = NULL;
  ....
  if (!(error->domain = G_CONVERT_ERROR &&
        error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
  ....
}

Programmierer verwenden oft fälschlicherweise den Zuweisungsoperator '=' anstelle des Vergleichsoperators '=='. Der Code wird kompiliert, und einige Compiler generieren möglicherweise eine Warnung zu diesem Problem. Aber Sie schalten unerwünschte Warnungen nicht aus und machen solche Fehler nicht, oder? Der Code sollte wahrscheinlich so aussehen:

if (!(error->domain == G_CONVERT_ERROR &&
        error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE))

Im nächsten Beispiel warnt der Analysator vor einer bedingten if-Anweisung, die einen Ausdruck enthält, der immer ein und denselben Wert ergibt:

V560 Ein Teil des bedingten Ausdrucks ist immer falsch:!auto_mnemonics. gtklabel.c 2693

static void
gtk_label_set_markup_internal (....)
{
  ....

  gboolean enable_mnemonics = TRUE;
  gboolean auto_mnemonics = TRUE;

  g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
                "gtk-enable-mnemonics", &enable_mnemonics,
                NULL);

  if (!(enable_mnemonics && priv->mnemonics_visible &&
        (!auto_mnemonics ||
         (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
          (!priv->mnemonic_widget ||
           gtk_widget_is_sensitive (priv->mnemonic_widget))))))
  ....
}

Es sieht auf den ersten Blick nicht nach einem Fehler aus. Aber wenn Sie genau hinsehen, werden Sie feststellen, dass die Variable „enable_mnemonics“ in der Nähe der Variablen „auto_mnemonics“ erstellt und dann mit einem Wert aus den Einstellungen initialisiert wird. Vielleicht muss auch der Wert für 'auto_mnemonics' auf ähnliche Weise abgerufen worden sein. Und wenn nicht, dann sollte die Prüfung der Bedingung '!auto_mnemonics' gelöscht werden, denke ich.

Noch eine Warnung bezüglich der 'auto_mnemonics'-Variablen:

V560 Ein Teil des bedingten Ausdrucks ist immer falsch:!auto_mnemonics. gtklabel.c 2923

static void
gtk_label_set_pattern_internal (....)
{
  ....
  gboolean enable_mnemonics = TRUE;
  gboolean auto_mnemonics = TRUE;

  ....
  g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
                "gtk-enable-mnemonics", &enable_mnemonics,
                NULL);

  if (enable_mnemonics && priv->mnemonics_visible && pattern &&
      (!auto_mnemonics ||
       (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
        (!priv->mnemonic_widget ||
         gtk_widget_is_sensitive (priv->mnemonic_widget)))))
  ....
}

Und hier ist eine Warnung vor einem gefährlichen Vergleich einer vorzeichenlosen Variablen vom Typ 'guint' mit einer vorzeichenbehafteten Konstante:

V605 Erwägen Sie, den Ausdruck zu überprüfen. Ein vorzeichenloser Wert wird mit der Zahl -3 verglichen. gtktextview.c 9162

V605 Erwägen Sie, den Ausdruck zu überprüfen. Ein vorzeichenloser Wert wird mit der Zahl -1 verglichen. gtktextview.c 9163

struct GtkTargetPair {
  GdkAtom   target;
  guint     flags;
  guint     info;
};

typedef enum
{
  GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS = - 1,
  GTK_TEXT_BUFFER_TARGET_INFO_RICH_TEXT       = - 2,
  GTK_TEXT_BUFFER_TARGET_INFO_TEXT            = - 3
} GtkTextBufferTargetInfo;

static void
gtk_text_view_target_list_notify (....)
{
  ....
  if (pair->info >= GTK_TEXT_BUFFER_TARGET_INFO_TEXT &&
      pair->info <= GTK_TEXT_BUFFER_TARGET_INFO_BUFFER_CONTENTS)
  ....
}

Compiler geben auch Warnungen bei solchen Vergleichen aus, aber sie werden oft zu Unrecht von Programmierern abgeschaltet. In diesem Beispiel wird ein signierter Typ implizit in unsigned umgewandelt. Es ist nicht so schlimm, wenn der Programmierer beim Schreiben der Bedingung diese Möglichkeit vorgesehen hat, aber selbst dann ist dieser Code alles andere als gut. Sie sollten zumindest eine explizite Konvertierung verwenden, um zu zeigen, dass Sie verstehen, was passiert. Wenn 'pair->info' nur Werte aus der 'GtkTextBufferTargetInfo'-Aufzählung zugewiesen werden können, warum dann nicht die info-Variable vom gleichen Typ machen? Und wenn ihm auch andere Werte zugewiesen werden können, ist dieser Ansatz insgesamt unsicher.

Die letzte Warnung, die wir diskutieren werden, befasst sich mit überlappenden Bereichen in 'if...elseif'-Bedingungen:

V695 Bereichsüberschneidungen sind innerhalb von bedingten Ausdrücken möglich. Beispiel:if (A <5) { .... } else if (A <2) { .... }. Überprüfen Sie die Zeilen:580, 587. broadway-server.c 587

static void
parse_input (BroadwayInput *input)
{
  ....
  while (input->buffer->len > 2)
    {
      ....
      if (payload_len > 125)
        {
          ....
        }
      else if (payload_len > 126)
        {
          ....
        }
      ....
    }
}

Wie aus dem Code ersichtlich, führt jeder Wert von „payload_len“, der größer als 125 ist, zur Ausführung der Verzweigung „if (payload_len> 125)“, während die Verzweigung „else if (payload_len> 126)“ ein Sonderfall dieses Originals ist überprüfen. Daher wird der Code in der 'elseif'-Bedingung niemals ausgeführt. Die Entwickler müssen es untersuchen und beheben.

Schlussfolgerung

Die Analyse des Codes des GTK+-Toolkits zeigt, dass er sowohl gewöhnliche Tippfehler als auch interessantere Fehler enthält, die behoben werden müssen. Statische Analysatoren sind sehr gut darin, solche Fehler in früheren Entwicklungsstadien zu eliminieren; Sie helfen Entwicklern, Zeit zu sparen, die für die Entwicklung neuer Funktionen aufgewendet werden kann, anstatt für Debugging und manuelle Fehlersuche. Denken Sie daran, dass Sie den statischen Analysator PVS-Studio kostenlos testen können, indem Sie ihn hier herunterladen.