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

Код проекта GTK 4 достойный

Редко я склонен набивать много ошибок в статье. Так было с недавним постом «Espressif IoT Framework Development: 71 выстрел в ногу». В этот раз ограничусь 21 ошибкой в ​​честь 2021 года :). Кроме того, следует отметить высокое качество проекта GTK 4, а также малую плотность ошибок.

Проект популярный, хорошо протестированный. Насколько мне известно, он уже тестируется такими инструментами, как инструмент статического анализа Clang, Coverity, AddressSanitizer, UndefinedBehavior Sanitizer. В принципе, с качеством кода все в порядке. Поэтому даже десяток найденных ошибок — это большой труд.

Ознакомьтесь со статьей Святослава Размыслова GTK: Первый анализатор в цифрах о процессе проверки GTK 4 и фильтрации сообщений отчетов. После первой фильтрации отчет показал 581 предупреждение первого и второго уровня достоверности. Третьего уровня я не рассматривал. Итак, мы получили 581 предупреждение и всего двадцать одну ошибку, добавленную в статью: этого достаточно? Это нормально.

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

Представьте, что вы видите следующий код:

bool var;
var = true;
if (!var && foo)

Думаю, вы согласитесь, что код выглядит подозрительно. Зачем так писать? Может автор где-то забыл изменить значение переменной var? Код пахнет неправильно. Неудивительно, что анализатору это тоже не понравилось. Статический анализатор PVS-Studio выдает предупреждение «Часть условного выражения всегда ложна:! вар”. Разумное предупреждение? да.

Как всегда, на практике есть нюансы. Вот идентичный фрагмент кода из GTK 4, но ничего подозрительного и опасного в нем нет:

gboolean debug_enabled;
#ifdef G_ENABLE_DEBUG
  debug_enabled = TRUE;
#else
  debug_enabled = FALSE;
#endif
....
if (!debug_enabled && !keys[i].always_enabled)

Анализатор по-прежнему выдает предупреждение: V560 [CWE-570] Часть условного выражения всегда ложна:! отладка_включена. gdk.c 281

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

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

Замеченные ошибки

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

Опечатки

Фрагмент N1. Довольно опечатка в цикле

void
gsk_vulkan_image_upload_regions (GskVulkanImage    *self,
                                 GskVulkanUploader *uploader,
                                 guint              num_regions,
                                 GskImageRegion    *regions)
{
  ....
  for (int i = 0; i < num_regions; i++)
  {
    m = mem + offset;
    if (regions[i].stride == regions[i].width * 4)
    {
      memcpy (m, regions[i].data, regions[i].stride * regions[i].height);
    }
    else
    {
      for (gsize r = 0; r < regions[i].height; i++)          // <=
        memcpy (m + r * regions[i].width * 4,
                regions[i].data + r * regions[i].stride, regions[i].width * 4);
    }
    ....
  }
  ....
}

Предупреждение PVS-Studio: V533 [CWE-691] Вероятно, внутри оператора for увеличивается неправильная переменная. Подумайте о пересмотре «i». gskvulkaanimage.c 721

Обратите внимание, что во вложенном цикле увеличивается не переменная r, а i. Нет нужды комментировать. Это золотая классика!

Фрагмент цикла N2, который не выполняется

В предыдущем случае функция могла запустить цикл с неконтролируемым количеством итераций. Это заканчивалось, когда функция memcpy записывала что-то совсем не в то место. В результате мы получим Segmentation fault.

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

GtkCssValue *
_gtk_css_border_value_parse (GtkCssParser           *parser,
                             GtkCssNumberParseFlags  flags,
                             gboolean                allow_auto,
                             gboolean                allow_fill)
{
  ....
  guint i;
  ....
  for (; i < 4; i++)        // <=
  {
    if (result->values[(i - 1) >> 1])
      result->values[i] = _gtk_css_value_ref (result->values[(i - 1) >> 1]);
  }
  result->is_computed = TRUE;
  for (; i < 4; i++)        // <=
    if (result->values[i] && !gtk_css_value_is_computed (result->values[i]))
    {
      result->is_computed = FALSE;
      break;
    }
  ....
}

Предупреждение PVS-Studio: V621 [CWE-835] Рассмотрите возможность проверки оператора for. Возможно, что цикл будет выполняться некорректно или вообще не будет выполняться. gtkcssbordervalue.c 221

После завершения первого цикла значение счетчика i равно 4. Следовательно, второй цикл не будет выполняться за одну итерацию. Не хватает сброса счетчика на 0.

Фрагмент N3, повторно использующий одну и ту же константу

static void
gtk_list_base_class_init (GtkListBaseClass *klass)
{
  ....
  properties[PROP_ORIENTATION] =
    g_param_spec_enum ("orientation",
                       P_("Orientation"),
                       P_("The orientation of the orientable"),
                       GTK_TYPE_ORIENTATION,
                       GTK_ORIENTATION_VERTICAL,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY |
                                           G_PARAM_EXPLICIT_NOTIFY);
  ....
}

Предупреждение PVS-Studio: V501 Слева и справа от оператора «|» одинаковые подвыражения G_PARAM_EXPLICIT_NOTIFY. gtklistbase.c 1151

Константа G_PARAM_EXPLICIT_NOTIFY используется дважды для создания маски. Программист явно намеревался использовать разные константы, и в результате в маске выставлены не все нужные биты.

Фрагмент N4. Путаница в порядке аргументов

Во-первых, давайте посмотрим на объявление функции post_insert_fixup . Обратите внимание на порядок формальных аргументов char_count_delta и line_count_delta.

static void    post_insert_fixup    (GtkTextBTree     *tree,
                                     GtkTextLine      *insert_line,
                                     int               char_count_delta,
                                     int               line_count_delta);

А теперь давайте внимательно посмотрим на фрагмент кода, где вызывается эта функция:

void
_gtk_text_btree_insert (GtkTextIter *iter,
                        const char *text,
                        int          len)
{
  ....
  int line_count_delta;                /* Counts change to total number of
                                        * lines in file.
                                        */
  int char_count_delta;                /* change to number of chars */
  ....
  post_insert_fixup (tree, line, line_count_delta, char_count_delta);
  ....
}

Предупреждение PVS-Studio: V764 [CWE-683] Возможен неверный порядок аргументов, передаваемых в функцию post_insert_fixup: line_count_delta и char_count_delta. gtktextbtree.c 1230

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

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

Фрагмент N5. Еще один впечатляющий пример путаницы аргументов

Обратите внимание на аргументы вызываемой функции:

static guint
translate_keysym (GdkX11Keymap   *keymap_x11,
                  guint           hardware_keycode,
                  int             group,
                  GdkModifierType state,
                  int            *effective_group,
                  int            *effective_level)
{
 ....
}

Неудачный вызов функции, указанный выше:

static gboolean
gdk_x11_keymap_translate_keyboard_state (GdkKeymap       *keymap,
                                         guint            hardware_keycode,
                                         GdkModifierType  state,
                                         int              group,
                                         guint           *keyval,
                                         int             *effective_group,
                                         int             *level,
                                         GdkModifierType *consumed_modifiers)
{
  ....
  tmp_keyval = translate_keysym (keymap_x11, hardware_keycode,
                                 group, state,
                                 level, effective_group);   // <=
  ....
}

Предупреждение PVS-Studio: V764 [CWE-683] Возможен неправильный порядок аргументов, передаваемых в функцию «translate_keysym»: «уровень» и «эффективная_группа». gdkkeys-x11.c 1386

На этот раз что-то накосячило в работе с клавиатурой. Это снова опечатка: фактические аргументы level и efficient_group перепутаны.

Если кто-то еще не решил скачать и попробовать PVS-Studio, то сейчас самое время :). Вы действительно любите замечать такие ошибки, только просматривая код? И избави нас всех от борьбы с ними в отладчике!

Фрагмент N6 Забытая звездочка (*)

gboolean
gtk_check_compact_table (...., int n_compose, ....)
{
  ....
  guint16 *seq_index;
  ....
  seq_index = bsearch (compose_buffer,
                       table->data,
                       table->n_index_size,
                       sizeof (guint16) * table->n_index_stride,
                       compare_seq_index);
  if (!seq_index)
    return FALSE;
  if (seq_index && n_compose == 1)
    return TRUE;
  ....
}

Предупреждение PVS-Studio: V560 [CWE-571] Часть условного выражения всегда истинна: ​​seq_index. gtkimcontextsimple.c 475

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

if (!seq_index)
  return FALSE;
if (*seq_index && n_compose == 1)
  return TRUE;

Фрагмент N7-N9. Повторяющиеся задания

static void
gtk_message_dialog_init (GtkMessageDialog *dialog)
{
  GtkMessageDialogPrivate *priv = ....;
  ....
  priv->has_primary_markup = FALSE;
  priv->has_secondary_text = FALSE;
  priv->has_primary_markup = FALSE;
  priv->has_secondary_text = FALSE;
  ....
}

Предупреждения PVS-Studio:

  • V519 [CWE-563] Переменной ‘priv-›has_primary_markup’ дважды подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 262, 264. gtkmessagedialog.c 264
  • V519 [CWE-563] Переменной ‘priv-›has_secondary_text’ дважды подряд присваиваются значения. Возможно, это ошибка. Проверить строки: 263, 265. gtkmessagedialog.c 265

Здесь этот блок кода повторяется два раза:

priv->has_primary_markup = FALSE;
priv->has_secondary_text = FALSE;

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

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

  • V519 [CWE-563] Переменной «self-›state» два раза подряд присваиваются значения. Возможно, это ошибка. Контрольные строки: 2851, 2855. gdkevents.c 2855
  • V519 [CWE-563] Переменной «display-›width» присваиваются значения дважды подряд. Возможно, это ошибка. Проверить строки: 2635, 2640. gtktextlayout.c 2640

Проблемы с нулевыми указателями

Фрагмент N10 Использование указателя перед проверкой

static gboolean
on_flash_timeout (GtkInspectorWindow *iw)
{
  iw->flash_count++;
  gtk_highlight_overlay_set_color (GTK_HIGHLIGHT_OVERLAY (iw->flash_overlay),
                               &(GdkRGBA) { 
                                   0.0, 0.0, 1.0,
                                   (iw && iw->flash_count % 2 == 0) ? 0.0 : 0.2
                               });
  ....
}

Предупреждение PVS-Studio: V595 [CWE-476] Указатель ‘iw’ использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 194, 199. inspect-button.c 194

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

(iw && iw->flash_count % 2 == 0)

Чтобы исправить ситуацию, нужно добавить еще одну проверку:

if (iw)
  iw->flash_count++;
gtk_highlight_overlay_set_color (GTK_HIGHLIGHT_OVERLAY (iw->flash_overlay),
                             &(GdkRGBA) { 
                                 0.0, 0.0, 1.0,
                                 (iw && iw->flash_count % 2 == 0) ? 0.0 : 0.2
                             });

Однако этого исправления все равно будет недостаточно :). Если мы присмотримся, то увидим еще одно разыменование при оценке аргументов:

GTK_HIGHLIGHT_OVERLAY (iw->flash_overlay)

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

Фрагмент N11 Многократное использование указателя перед проверкой

static void
cups_dispatch_watch_finalize (GSource *source)
{
  GtkPrintCupsDispatchWatch *dispatch;
  ....
  const char *username;
  char         hostname[HTTP_MAX_URI];
  char        *key;
  httpGetHostname (dispatch->request->http, hostname, sizeof (hostname));
  if (is_address_local (hostname))
    strcpy (hostname, "localhost");
  if (dispatch->backend->username != NULL)                     // <=
    username = dispatch->backend->username;                    // <=
  else
    username = cupsUser ();
  key = g_strconcat (username, "@", hostname, NULL);
  GTK_NOTE (PRINTING,
      g_print ("CUPS backend: removing stored password for %s\n", key));
  g_hash_table_remove (dispatch->backend->auth, key);          // <=
  g_free (key);
  if (dispatch->backend)                                       // <=
    dispatch->backend->authentication_lock = FALSE;
  ....
}

Предупреждение PVS-Studio: V595 [CWE-476] Указатель «dispatch-›backend» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 1603, 1613. gtkprintbackendcups.c 1603

Это еще более «бесстрашный» код :). Авторы разыменовывают указатель dispatch-›backend (см. фрагменты кода, выделенные комментариями). Только после того, как они вспомнили о потенциально нулевом указателе, они выписали чек.

if (dispatch->backend)

Но уже поздно :).

Фрагмент N12 Никаких мер безопасности, если оба указателя пусты

static GskRenderNode *
gtk_snapshot_collect_blend_top (GtkSnapshot      *snapshot,
                                GtkSnapshotState *state,
                                GskRenderNode   **nodes,
                                guint             n_nodes)
{
  GskRenderNode *bottom_node, *top_node, *blend_node;
  GdkRGBA transparent = { 0, 0, 0, 0 };
  top_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
  bottom_node = state->data.blend.bottom_node != NULL
              ? gsk_render_node_ref (state->data.blend.bottom_node)
              : NULL;
  g_assert (top_node != NULL || bottom_node != NULL);
  if (top_node == NULL)
    top_node = gsk_color_node_new (&transparent, &bottom_node->bounds);
  if (bottom_node == NULL)
    bottom_node = gsk_color_node_new (&transparent, &top_node->bounds);
  ....
}

V595 [CWE-476] Указатель «bottom_node» использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 1189, 1190. gtksnapshot.c 1189

Оба указателя top_node и bottom_node не должны быть нулевыми. Мы узнаем об этом из строки:

g_assert (top_node != NULL || bottom_node != NULL);

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

if (top_node == NULL && bottom_node == NULL)
{
  g_assert (false);
  return NULL;
}

Неудачные или избыточные проверки

Фрагмент N13: избыточная проверка

static void
stash_desktop_startup_notification_id (void)
{
  const char *desktop_startup_id;
  desktop_startup_id = g_getenv ("DESKTOP_STARTUP_ID");
  if (desktop_startup_id && *desktop_startup_id != '\0')
    {
      if (!g_utf8_validate (desktop_startup_id, -1, NULL))
        g_warning ("DESKTOP_STARTUP_ID contains invalid UTF-8");
      else
        startup_notification_id =
          g_strdup (desktop_startup_id ? desktop_startup_id : "");
    }
  g_unsetenv ("DESKTOP_STARTUP_ID");
}

Предупреждение PVS-Studio: V547 [CWE-571] Выражение ‘desktop_startup_id’ всегда истинно. gdk.c 176

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

Давайте упростим этот код, убрав вторую проверку указателя:

desktop_startup_id = g_getenv ("DESKTOP_STARTUP_ID");
if (desktop_startup_id && *desktop_startup_id != '\0')
  {
    if (!g_utf8_validate (desktop_startup_id, -1, NULL))
      g_warning ("DESKTOP_STARTUP_ID contains invalid UTF-8");
    else
      startup_notification_id = g_strdup (desktop_startup_id);
  }

Переменная фрагмента N14 не меняет свое значение

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

Еще один случай, когда мы можем смело опускать проверку:

#define MAX_LIST_SIZE 1000
static void
gtk_recent_manager_real_changed (GtkRecentManager *manager)
{
  ....
  int age;
  int max_size = MAX_LIST_SIZE;
  ....
  ...//The max_size variable does not change here.
  ....
  if (age == 0 || max_size == 0 || !enabled)
  {
    g_bookmark_file_free (priv->recent_items);
    priv->recent_items = g_bookmark_file_new ();
    priv->size = 0;
  }
  else
  {
    if (age > 0)
      gtk_recent_manager_clamp_to_age (manager, age);
    if (max_size > 0)
      gtk_recent_manager_clamp_to_size (manager, max_size);
  }
  ....
}

Предупреждение PVS-Studio: V547 [CWE-571] Выражение max_size › 0 всегда верно. gtkrecentmanager.c 480

Дело в том, что значение переменной max_size после ее объявления и инициализации уже не меняется. Это довольно странно. Этот код может быть как избыточным, так и содержать ошибку в своей логике, если кто-то где-то забыл изменить значение переменной.

Примечание. Внимательный читатель может задать вопрос: почему нет предупреждения на часть подвыражения «max_size == 0»? Что ж, это так. Я просто пропустил это во время беглого просмотра отчета. Я тоже не обратил внимания на этот момент при написании статьи. Вот это предупреждение: V560 [CWE-570] Часть условного выражения всегда ложна: max_size == 0. gtkrecentmanager.c 470.

Фрагмент N15, N16. Использование индекса до его проверки

static void
action_handle_method (GtkAtSpiContext        *self,
                      const char             *method_name,
                      GVariant               *parameters,
                      GDBusMethodInvocation  *invocation,
                      const Action           *actions,
                      int                     n_actions)
{
  ....
  int idx = -1;
  g_variant_get (parameters, "(i)", &idx);
  const Action *action = &actions[idx];
  if (idx >= 0 && idx < n_actions)
    g_dbus_method_invocation_return_value (
      invocation, g_variant_new ("(s)", action->name));
  else
    g_dbus_method_invocation_return_error (invocation,
                                           G_IO_ERROR,
                                           G_IO_ERROR_INVALID_ARGUMENT,
                                           "Unknown action %d",
                                           idx);
  ....
}

Предупреждение PVS-Studio: V781 [CWE-129] Значение переменной idx проверяется после ее использования. Возможно, ошибка в логике программы. Контрольные строки: 71, 73. gtkatspiaction.c 71

Сначала переменная idx используется для доступа к элементам массива:

const Action *action = &actions[idx];

И только потом проверяется, не является ли оно отрицательным или слишком большое значение в этой переменной:

if (idx >= 0 && idx < n_actions)

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

Аналогичный случай: V781 [CWE-129] Значение переменной «idx» проверяется после ее использования. Возможно, ошибка в логике программы. Контрольные строки: 132, 134. gtkatspiaction.c 132

Фрагмент N17, N18. Недоступный код

static gboolean
parse_n_plus_b (GtkCssParser *parser,
                int           before,
                int          *a,
                int          *b)
{
  const GtkCssToken *token;
  token = gtk_css_parser_get_token (parser);
  if (gtk_css_token_is_ident (token, "n"))
    {
      ....
      return parse_plus_b (parser, FALSE, b);
    }
  else if (gtk_css_token_is_ident (token, "n-"))
    {
      ....
      return parse_plus_b (parser, TRUE, b);
    }
  else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT) &&
           string_has_number (token->string.string, "n-", b))
    {
      ....
      return TRUE;
    }
  else
    {
      *b = before;
      *a = 0;
      return TRUE;
    }
  
  gtk_css_parser_error_syntax (parser, "Not a valid an+b type");
  return FALSE;
}

Предупреждение PVS-Studio: V779 [CWE-561] Обнаружен недостижимый код. Возможно, что ошибка присутствует. gtkcssselector.c 1077

Функция содержит последовательность if-else-if-else…. При этом тело каждого условного оператора заканчивается выходом из функции. Это странно, так как в конце функции есть кусок кода, который никогда не получит управление.

Еще один похожий случай с недоступным кодом: V779 [CWE-561] Обнаружен недостижимый код. Возможно, что ошибка присутствует. gtktreemodelfilter.c 3289

Разное

Фрагмент N19, N20. Целочисленное деление

static void
gtk_paint_spinner (GtkStyleContext *context,
                   cairo_t         *cr,
                   guint            step,
                   int              x,
                   int              y,
                   int              width,
                   int              height)
{
  GdkRGBA color;
  guint num_steps;
  double dx, dy;
  ....
  dx = width / 2;
  dy = height / 2;
  ....
  cairo_move_to (cr,
                 dx + (radius - inset) * cos (i * G_PI / half),
                 dy + (radius - inset) * sin (i * G_PI / half));
  cairo_line_to (cr,
                 dx + radius * cos (i * G_PI / half),
                 dy + radius * sin (i * G_PI / half));
  ....
}

Предупреждения PVS-Studio:

  • V636 [CWE-682] Выражение «width / 2» было неявно преобразовано из типа «int» в тип «double». Рассмотрите возможность использования явного приведения типа, чтобы избежать потери дробной части. Пример: двойной A = (двойной)(X)/Y;. gtkcellrendererspinner.c 412
  • V636 [CWE-682] Выражение «height / 2» было неявно преобразовано из типа «int» в тип «double». Рассмотрите возможность использования явного приведения типа, чтобы избежать потери дробной части. Пример: двойной A = (двойной)(X)/Y;. gtkcellrendererspinner.c 413

Результаты целочисленного деления записываются в переменные dx и dy. Эти переменные относятся к типу double, что вызывает сомнения. Скорее всего это просто промах. Вот возможная правильная версия:

dx = width / 2.0;
dy = height / 2.0;

Подобные подозрительные разделы обнаружены в фрагменте кода, на который указывают эти два предупреждения:

  • V636 [CWE-682] Выражение «width / 2» было неявно преобразовано из типа «int» в тип «double». Рассмотрите возможность использования явного приведения типа, чтобы избежать потери дробной части. Пример: двойной A = (двойной)(X)/Y;. gtkswitch.c 255
  • V636 [CWE-682] Выражение «width / 2» было неявно преобразовано из типа «int» в тип «double». Рассмотрите возможность использования явного приведения типа, чтобы избежать потери дробной части. Пример: двойной A = (двойной)(X)/Y;. gtkswitch.c 257

Фрагмент N21 Пароль не может быть очищен в памяти

В качестве особого удовольствия я сохранил очень интересный случай. Распространенной ошибкой является использование memset для очистки данных в памяти. Компиляторы любят удалять такие вызовы функций в целях оптимизации. С точки зрения C и C++, если область памяти больше не используется после заполнения, то ее вообще не нужно заполнять. Другими словами, компилятор удаляет запись значения в переменную, если из этой переменной не происходит чтения.

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

Что интересного в случае с GTK 4? Дело в том, что вызов функции free происходит через промежуточную функцию и здесь становится сложнее предсказать, начнет компилятор оптимизацию или нет.

В GTK 4 мы видим функцию g_free, которая освобождает память: Она реализована следующим образом:

void
g_free (gpointer mem)
{
  free (mem);
  TRACE(GLIB_MEM_FREE((void*) mem));
}

Всегда ли g_free является оболочкой free? Начиная с GLib 2.46 это всегда так. Документация охватывает этот вопрос следующим образом:

Важно сопоставить g_malloc() (и оболочки, такие как g_new()) с g_free(), g_slice_alloc() (и оболочки, такие как g_slice_new()) с g_slice_free(), обычный malloc() с free(), и (если вы используете C++) new с помощью delete и new[] с delete[]. В противном случае могут произойти плохие вещи, поскольку эти распределители могут использовать разные пулы памяти (а также конструкторы и деструкторы вызовов new/delete).

Поскольку в GLib 2.46 функция g_malloc() жестко закодирована, чтобы всегда использовать системную реализацию malloc.

Таким образом, поскольку память в g_malloc выделяется с помощью malloc, для ее освобождения всегда должна вызываться функция free.

Теперь давайте посмотрим на код проблемы.

void overwrite_and_free (gpointer data)
{
  char *password = (char *) data;
  if (password != NULL)
    {
      memset (password, 0, strlen (password));
      g_free (password);
    }
}

Предупреждение PVS-Studio: V597 [CWE-14] Компилятор мог удалить вызов функции memset, которая используется для сброса объекта password. Функцию memset_s() следует использовать для удаления личных данных. gtkprintbackendcups.c 848

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

Очень пикантная неприятная ошибка из области информационной безопасности.

Вывод

Статический анализатор PVS-Studio поддерживает множество сценариев использования инструмента. Во-первых, имеется в виду возможность его интеграции с IntelliJ IDEA, Rider, IncrediBuild, Jenkins, PlatformIO, Travis CI, GitLab CI/CD, CircleCI, TeamCity, Visual Studio и так далее. Во-вторых, есть различные варианты его введения, настройки, использования уведомлений. На самом деле, мы даже не рады, что в нем так много разных вещей. Просто невозможно сделать краткую лаконичную документацию, как это было, например, 10 лет назад, когда PVS-Studio был просто плагином для Visual Studio. Наверняка никто не читает существующую объемную документацию от корки до корки :). В итоге разработчики наступают на одни и те же грабли, допускают одни и те же ошибки в реализации, задают аналогичные вопросы в поддержку.

К сожалению, исправить ситуацию с помощью более понятного интерфейса невозможно. Собственно, в некоторых случаях и интерфейса нет :). Есть только настройки для конфигурации, интеграции, например, с SonarQube.

Поэтому, поразмыслив, мы пришли к идее размещения коротких видео-уроков по некоторым аспектам использования PVS-Studio. Так вы сможете быстрее находить ответы и решать проблемы. Что еще более важно, вы можете подписаться на канал и шаг за шагом узнавать о новых функциях, просматривая наши новые видео-подсказки после их публикации. Лучше знакомиться с PVS-Studio по частям, чем прошерстить всю документацию за один раз :). Маловероятно, что это сработает, но давайте попробуем! Рассмотрим подписку: Возможности PVS-Studio (YouTube).