Айтор: Роман Фомичев

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

Введение

GTK+ (аббревиатура от GIMP ToolKit) — кроссплатформенный набор инструментов для создания графических пользовательских интерфейсов. Он лицензирован в соответствии с условиями LGPL, что позволяет использовать его как бесплатному, так и проприетарному программному обеспечению. Это один из самых популярных наборов инструментов для оконных систем Wayland и X11, наряду с Qt.

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

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

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

V728 Излишняя проверка может быть упрощена. Оператор || окружен противоположными выражениями !mount и 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 Излишняя проверка может быть упрощена. Оператор || окружен противоположными выражениями ret и !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 Рассмотрите возможность проверки ‘str[0] == ‘\0’ || str[0] != Выражение ‘U’’. Выражение является избыточным или содержит опечатку. 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;

Все эти проблемы на самом деле не являются ошибками. Код будет успешно выполнен; просто в нем есть какие-то ненужные проверки, которые тоже будут выполняться.

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

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

Вот несколько примеров ошибок, вызванных неправильным использованием копипасты:

V523 Оператор тогда эквивалентен оператору еще. 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 (ориентация == GTK_ORIENTATION_HORIZONTAL)» и соответствующий блок else содержат один и тот же код. Это может быть как неполный функционал, так и баг.

V501 Слева и справа от оператора одинаковые подвыражения (box-›corner[GTK_CSS_TOP_RIGHT].horizontal). gtkcssshadowvalue.c 685

V501 Слева и справа от оператора одинаковые подвыражения (box-›corner[GTK_CSS_TOP_LEFT].horizontal). 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 Слева и справа от оператора | находятся одинаковые подвыражения G_PARAM_EXPLICIT_NOTIFY. 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 Слева и справа от оператора | одинаковые подвыражения G_PARAM_DEPRECATED. 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 Переменной impl_class-›set_functions два раза подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 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 Переменной impl_class-›set_functions два раза подряд присваиваются значения. Возможно, это ошибка. Контрольные строки: 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 Странно, что тело функции gtk_mirror_bin_get_preferred_height полностью эквивалентно телу функции gtk_mirror_bin_get_preferred_width. 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.width, вероятно, следует использовать «requisition.height». Тогда это должно выглядеть так:

*minimum = *natural = requisition.height;

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

Вот еще один пример, где, по-моему, тоже перепутаны ширина и длина:

V524 Странно, что тело функции gtk_hsv_get_preferred_height полностью эквивалентно телу функции gtk_hsv_get_preferred_width. 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);
}

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

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

V549 Первый аргумент функции strcmp равен второму аргументу. 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 Переменная tmp_info присваивается самой себе. 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»: только тогда код после «пока» имел бы смысл. Попробуем исправить:

info = tmp_info;

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

Обработка указателя

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

V528 Странно, что указатель на тип char сравнивается со значением \0. Вероятно имелось в виду: *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 Странно, что указатель на тип char сравнивается со значением \0. Вероятно имелось в виду: *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 Указатель завершения использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 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) // <=
    {
      ....
    }
  ....
}

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

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

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

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

Если указатель здесь окажется нулевым, мы получим неопределенное поведение. Либо отсутствует проверка указателя завершения ранее в коде, либо более поздняя проверка не имеет смысла и не нужна.

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

V595 Указатель dispatch-›backend использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 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 Указатель impl-›toplevel использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 514, 524. gdkwindow-x11.c 514

V595 Указатель pointer_info использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 9610, 9638. gdkwindow.c 9610

V595 Указатель elt использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 2218, 2225. gtktreemodelfilter.c 2218

V595 Указатель tmp_list использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 5817, 5831. gtktreeview.c 5817

V595 Указатель «dispatch-›data_poll» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 1470, 1474. gtkprintbackendcups.c 1470

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

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

В следующем примере автор забыл написать операторы «break» в конце операторов «case»:

V519 Переменной type дважды подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 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 Переменная i используется для этого цикла и для внешнего цикла. Проверить строки: 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 Возможен переполнение массива. Значение индекса i + 1 может достигать 21. gtkcssselector.c 1219

V557 Возможен переполнение массива. Значение индекса i + 1 может достигать 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[i+1]» приведет к индексации за пределами массива.

Следующая потенциальная ошибка выглядит как опечатка:

V559 Подозрительное присваивание внутри условного выражения оператора если. 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 Часть условного выражения всегда ложна: !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))))))
  ....
}

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

Еще одно предупреждение по поводу переменной auto_mnemonics:

V560 Часть условного выражения всегда ложна: !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 со знаковой константой:

V605 Попробуйте проверить выражение. Значение без знака сравнивается с числом -3. gtktextview.c 9162

V605 Попробуйте проверить выражение. Значение без знака сравнивается с числом -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», то почему бы не сделать информационную переменную того же типа? А если ему могут быть присвоены и другие значения, то такой подход вообще небезопасен.

Последнее предупреждение, которое мы обсудим, касается перекрывающихся диапазонов в условиях if…elseif:

В условных выражениях возможны пересечения диапазонов V695. Пример: если (А ‹ 5) { …. } иначе если (A ‹ 2) { …. }. Контрольные строки: 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, скачав его здесь.