Проверка кроссплатформенной библиотеки элементов интерфейса GTK+ с помощью PVS-Studio




Для упрощения процесса разработки графического интерфейса приложения используются библиотеки элементов интерфейса. Одной из таких библиотек является GTK+. И именно этот проект я выбрал для своей первой статьи, посвященной анализатору PVS-Studio. Итак, я использовал PVS-Studio для выявления дефектов в коде GTK+. Анализатор выдал довольно большое количество предупреждений, сигнализирующих об ошибках или подозрительных местах в коде библиотеки. Некоторые предупреждения достаточно серьезны. Все ошибки в статье не поместятся поэтому расскажу о некоторых из них, самых характерных.

Picture 3

Введение

GTK+ (сокращение от GIMP ToolKit) — кроссплатформенная библиотека элементов интерфейса, имеет простой в использовании API, наряду с Qt является одной из двух наиболее популярных на сегодняшний день библиотек для X Window System.

Будучи изначально частью графического редактора GIMP, она развилась в отдельный проект и приобрела заметную популярность. GTK+ — свободное ПО, распространяемое на условиях GNU LGPL, позволяющей создавать как свободное, так и проприетарное программное обеспечение с использованием библиотеки. GTK+ является официальной библиотекой для создания графического интерфейса проекта GNU.

Мы проверили код библиотеки с помощью статического анализатора PVS-Studio и проанализировали полученные предупреждения. Код был проверен анализатором версии 6.02.

Избыточный код

Для начала рассмотрим предупреждения, связанные с построением логических выражений. Зачастую тут нет ошибок, просто присутствуют избыточные проверки, которые затрудняют понимание условия. Такие выражения можно значительно упростить.

V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions '!mount' and 'mount'. gtkplacesview.c 708

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

В данном примере мы видим избыточную проверку указателя 'mount'. Код можно модифицировать следующим образом:

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

Еще один подобный случай:

V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions 'ret' and '!ret'. gtktreeview.c 13682

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

Опять избыточная проверка, на этот раз булевой переменной 'ret'. Модифицируем код:

if (ret || tree == NULL)

V590 Consider inspecting the 'str[0] == '\0' || str[0] != 'U'' expression. The expression is excessive or contains a misprint. 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;
}

Проверка str[0] == '\0' избыточна, так как это частный случай выражения str[0] != 'U'. Можно упростить код, удалив излишнюю проверку:

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

Это были предупреждения, не приводящие к ошибкам, такой код будет исправно работать, но будут совершаться ненужные проверки.

Picture 2

Повторное использование кода

Работа программной индустрии во многом построена на повторном использовании уже написанного кода. Действительно зачем изобретать велосипед. Очень частой причиной ошибок становится копирование кода, который необходимо модифицировать. Зачастую программист пропускает кусок кода, который надо было исправить, что приводит к ошибкам в программе. Одной из сильных сторон статического анализатора PVS-Studio является как раз выявление подобных мест.

Рассмотрим несколько случаев использования copy-paste:

V523 The 'then' statement is equivalent to the 'else' statement. 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;
        }
    }
  ....
}

Видим одинаковый код в if-блоке 'if (orientation == GTK_ORIENTATION_HORIZONTAL)' и в else-блоке. Возможно функциональность еще не реализована, а может быть тут и ошибка.

V501 There are identical sub-expressions '(box->corner[GTK_CSS_TOP_RIGHT].horizontal)' to the left and to the right of the '>' operator. gtkcssshadowvalue.c 685

V501 There are identical sub-expressions '(box->corner[GTK_CSS_TOP_LEFT].horizontal)' to the left and to the right of the '>' 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);
      ....
    }
  ....
}

В макрос MAX подаются одинаковые переменные. Возможно забыли вставить правильные значения констант вместо 'GTK_CSS_TOP_RIGHT' и 'GTK_CSS_TOP_LEFT', а может сравнение должно было быть совсем с другой переменной.

V501 There are identical sub-expressions 'G_PARAM_EXPLICIT_NOTIFY' to the left and to the right of the '|' 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));
  ....
}

В этом примере или скопировали лишний раз константу 'G_PARAM_EXPLICIT_NOTIFY' или забыли заменить ее на другую.

Еще один подобный случай, на этот раз с константой 'G_PARAM_DEPRECATED':

V501 There are identical sub-expressions 'G_PARAM_DEPRECATED' to the left and to the right of the '|' 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));
  ....
}

Очень часто ошибки, связанные с копированием, скрываются в длинных списках инициализации. Программисту сложно заметить такие ошибки, поэтому на помощь приходит статический анализатор.

В следующем примере очень длинный список инициализации, неудивительно совершить какую-нибудь ошибку в таком случае:

V519 The 'impl_class->set_functions' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 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;
  ....
}

Сначала я подумал, что здесь по аналогии с вышестоящей инициализацией должна быть пара 'get-set': 'set_functions' и 'get_functions'. Но оказалось, что в структуре 'GdkWindowImplClass' нет поля 'get_functions'. Здесь возможно случайно размножили инициализацию, а может быть хотели заменить чем-то другим и забыли. В любом случае следует убедиться, что проинициализировано все что нужно, и удалить при необходимости лишнюю операцию.

Следующее предупреждение аналогично предыдущему:

V519 The 'impl_class->set_functions' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 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;
  ....
}

Опять двойное присвоение 'impl_class->set_functions', возможно оно перекочевало из предыдущего примера.

Иногда копируются целые функции, в которых выполняются практически идентичные действия, и после изменения заголовка программист забывает изменить тело функции. Поможем забывчивому программисту и исправим найденные ошибки:

V524 It is odd that the body of 'gtk_mirror_bin_get_preferred_height' function is fully equivalent to the body of 'gtk_mirror_bin_get_preferred_width' function. 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;
}

В функции gtk_mirror_bin_get_preferred_height скорее всего надо использовать 'requisition. height' вместо 'requisition.width', Правильный код должен вероятно выглядеть следующим образом:

 *minimum = *natural = requisition.height;

А может это не ошибка, а так и задумано, но выглядит странно.

Нашелся еще один случай, где на мой взгляд есть путаница с шириной и высотой:

V524 It is odd that the body of 'gtk_hsv_get_preferred_height' function is fully equivalent to the body of 'gtk_hsv_get_preferred_width' function. 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);
}

В этом случае, если и высота, и ширина вычисляются одинаково, возможно следует сделать одну функцию вместо двух, а если планировались какие-то дополнительные правки, то надо не забыть их сделать.

В следующем примере не совсем понятно какой аргумент должен использоваться вместо скопированного 'component_name', но однозначно подозрительно сравнивать строку саму с собой:

V549 The first argument of 'strcmp' function is equal to the second 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);
  ....
}

Продолжаем просматривать предупреждения, связанные с копированием кода:

V570 The 'tmp_info' variable is assigned to itself. 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)
    {
      ....
    }
  ....
}

Посмотрев на код кажется логичным, что присвоить значение хотели переменной 'info', только тогда код, находящийся за 'while', имеет смысл. Попробуем исправить:

info = tmp_info; 

На этом заканчиваем рассматривать ошибки, связанные с копированием кода. Можно сделать вывод, что это очень распространенный тип ошибок, которые могут скрываться длительное время. Нередко они сложны для поиска, так как их трудно заметить при быстром просмотре кода.

Работа с указателями

Следующая группа возможных ошибок связана с некорректным использованием указателей. Неаккуратная работа с указателями может привести к аварийному завершению приложения или его неопределенному поведению.

V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *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')
      ....
  ....
}

По адресу 'data->groups[0]' лежит 'gchar*', то есть тоже указатель, который сравнивать с '\0' некорректно. В текущем примере по факту происходит сравнение указателя 'data->groups[0]' с нулевым указателем. Если надо было убедиться, что указатель ненулевой, то правильный код должен быть следующим:


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

Если же автор кода хотел проверить символ лежащий по адресу 'data->groups[0]' на равенство терминальному нулю, тогда надо было разыменовать указатель:

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

Вот еще один такой пример, опять некорректное сравнение:

V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *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')
  ....
}

При использовании указателей в C/C++ следует соблюдать осторожность. Если нет уверенности, что по указателю есть данные следует обязательно проверять его на нулевое значение.

Обращению по нулевому указателю приведет к неопределенному поведению программы или ее аварийному завершению. Следующие предупреждения сообщают о вероятности такого обращения.

V595 The 'completion' pointer was utilized before it was verified against nullptr. Check lines: 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) // <=
    {
      ....
    }
  ....
}

В теле функции программист проверяет указатель 'completion' на нулевое значение, а потом использует его:

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

Исходя из этой проверки, программист допускает вероятность того, что указатель окажется нулевым. Но выше происходит обращение к этому указателю без его проверки на нулевое значение:

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

Если указатель в этом месте окажется нулевым, то мы получаем неопределенное поведение. Надо или проверить указатель 'completion' еще выше или та проверка, которая происходит ниже, бессмыслена.

Таких примеров в коде библиотеки больше десятка, поэтому рассмотрим только еще один случай:

V595 The 'dispatch->backend' pointer was utilized before it was verified against nullptr. Check lines: 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;
  ....
}

Проверка указателя 'dispatch->backend' происходит уже после того, когда мы к нему обращались, это тоже потенциально проблемное место.

Вот список подобных, потенциально проблемных, мест. Я не стал включать в этот список предупреждения где указатель проверяется в макросах. Возможно и в тех местах есть потенциальная возможность использования нулевых указателей. Но не исключаю, что программист просто использовал подходящий макрос в котором есть ненужная ему проверка.

V595 The 'impl->toplevel' pointer was utilized before it was verified against nullptr. Check lines: 514, 524. gdkwindow-x11.c 514

V595 The 'pointer_info' pointer was utilized before it was verified against nullptr. Check lines: 9610, 9638. gdkwindow.c 9610

V595 The 'elt' pointer was utilized before it was verified against nullptr. Check lines: 2218, 2225. gtktreemodelfilter.c 2218

V595 The 'tmp_list' pointer was utilized before it was verified against nullptr. Check lines: 5817, 5831. gtktreeview.c 5817

V595 The 'dispatch->data_poll' pointer was utilized before it was verified against nullptr. Check lines: 1470, 1474. gtkprintbackendcups.c 1470

Другие ошибки

В конце рассмотрим группу разнообразных предупреждений, сигнализирующих о возможных алгоритмических ошибках или опечатках.

В данном примере автор забыл расставить 'break' в конце 'case'-ов:

V519 The 'type' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 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];
    }
  ....
}

Независимо от того, какое из трех значений примет 'info', мы получим присвоение 'type = seltypes[STRING];'. Чтобы избежать этого, надо добавить оператор 'break':


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

Следующее место очень подозрительно, одна и та же переменная ('i') используется как счетчик для основного цикла и вложенного цикла:

V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 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));
        }
    ....
    }
  ....
}

Даже не знаю, чему будет равен 'i' после выполнения вложенного цикла, и как будет исполняться основной цикл после этого. Чтобы исключить неопределенность в этом месте, возможно лучше было бы использовать свой счетчик для вложенного цикла, например:

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

Еще парочка потенциально опасных использований счетчика в цикле:

V557 Array overrun is possible. The value of 'i + 1' index could reach 21. gtkcssselector.c 1219

V557 Array overrun is possible. The value of 'i + 1' index could reach 21. 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);
        ....
      }
       ....
    }
  ....
}

Мы видим цикл по количеству элементов в массиве 'pseudo_classes', я надеюсь, что этот цикл никогда не дойдет до последнего элемента, в противном случае конструкция 'pseudo_classes[i+1]' приведет нас к выходу за границы массива.

Следующее потенциально ошибочное место в коде похоже на опечатку:

V559 Suspicious assignment inside the condition expression of 'if' operator. 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))
  ....
}

Довольно часто случается, что вместо оператора сравнения '==' пишут присвоение '='. Код будет скомпилирован, часть компиляторов возможно выдаст предупреждение в такой ситуации. Вы же не блокируете ненужные предупреждения, и не допустите такой ошибки? Скорее всего код должен выглядеть так:

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

Теперь анализатор сообщает нам об использовании в условном операторе if выражения, значение которого всегда постоянно:

V560 A part of conditional expression is always false: !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))))))
  ....
}

В принципе нельзя сказать сразу, что это ошибка. Но если внимательно посмотреть, то можно увидеть, что рядом с переменной 'auto_mnemonics' создается переменная 'enable_mnemonics', а затем она инициализируется значением из настроек. Возможно здесь забыли взять таким же образом значение переменной 'auto_mnemonics'. Если же нет, тогда возможно стоит убрать проверку условия '!auto_mnemonics'.

И еще одно предупреждение с переменной 'auto_mnemonics':

V560 A part of conditional expression is always false: !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)))))
  ....
}

А вот предупреждение об опасном сравнении беззнаковой переменной типа 'guint' cо знаковой константой:

V605 Consider verifying the expression. An unsigned value is compared to the number -3. gtktextview.c 9162

V605 Consider verifying the expression. An unsigned value is compared to the number -1. 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)
  ....
}

Есть соответствующие предупреждения компилятора о подобных сравнениях, но зачастую они незаслуженно отключаются. В данном случае произойдет неявное приведение знакового типа к беззнаковому. Хорошо, если программист учел это в условии, но даже если это и так, то все равно такой код не очень хорош. Как минимум желательно использовать явное приведение, чтобы было видно, что программист понимает, что происходит в этом месте. Если 'pair->info' может принимать значения только из перечисления 'GtkTextBufferTargetInfo' тогда почему не сделать переменную info такого же типа. Если же в ней могут оказаться и другие значения, тогда может быть небезопасным использовать такой подход.

Последнее рассмотренное предупреждение сообщает о пересечении диапазонов в условии 'if...elseif':

V695 Range intersections are possible within conditional expressions. Example: if (A < 5) { .... } else if (A < 2) { .... }. Check lines: 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)
        {
          ....
        }
      ....
    }
}

Проанализировав код, мы видим что при любом значении 'payload_len', которое больше 125, будет исполняться ветка 'if (payload_len > 125)', а ветка 'else if (payload_len > 126)' является частным случаем исходной проверки. Поэтому код, находящийся в условии 'elseif' никогда не будет исполняться. Разработчику необходимо просмотреть этот код и скорректировать его.

Заключение

После анализа кода библиотеки GTK+, мы видим, что в ней есть, как и простые опечатки, так и более интересные ошибки, которые требуют исправления. Для устранения подобных ошибок на раннем этапе хорошо помогают статические анализаторы, которые экономят время разработчикам. Время, потраченное на отладку и поиск некоторых ошибок, можно будет использовать для разработки функциональности. Напоминаю, что вы можете опробовать работу статического анализатора PVS-Studio бесплатно. Скачать дистрибутив программы можно здесь.



Используйте PVS-Studio для поиска ошибок в C, C++ и C# коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

goto PVS-Studio;


А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++ и C#

goto PVS-Studio;
Мы используем cookie-файлы для анализа событий на нашем веб-сайте, что позволяет улучшить наш контент и сделать взаимодействие с пользователем более удобным. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Узнайте подробнее о cookie-файлах и политике конфиденциальности или скройте это уведомление, нажав на кнопку. Подробнее →
Не показывать