﻿# Выявляем опечатки в проекте GTK 4 с помощью PVS\-Studio

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

![0793_GTK_4_continue_ru/image1.png](https://import.viva64.com/docx/blog/0793_GTK_4_continue_ru/image1.png)

## У проекта GTK 4 хорошее качество кода

Далеко не всегда у меня бывает настроение выписать в статью побольше ошибок, как это было, например, в случае недавней публикации "[Espressif IoT Development Framework: 71 выстрел в ногу](https://pvs-studio.ru/ru/blog/posts/cpp/0790/)"\. В этот раз я ограничусь 21 ошибкой в честь 2021 года :\)\. Плюс, справедливости ради надо отметить высокое качество проекта GTK 4, и что плотность настоящих ошибок весьма мала\.

Проект популярен, хорошо тестируется и, как я понимаю, уже проверяется такими инструментами, как Clang static analysis tool, Coverity, AddressSanitizer, UndefinedBehavior Sanitizer\. В общем, с качеством кода всё хорошо, и найти хотя\-бы десяток ошибок \- это уже хорошее достижение\.

О процессе проверки GTK 4 и первичной фильтрации отчёта, вы можете прочитать в статье Святослава Размыслова "[GTK: Как выглядит первый запуск анализатора в цифрах](https://pvs-studio.ru/ru/blog/posts/cpp/0789/)"\. После первичной фильтрации в отчёте осталось 581 предупреждение первого и второго уровня достоверности \(на третий уровень я заглядывать не стал\)\. 581 предупреждение и только двадцать одна выписанная ошибка: разве не маловато? Это нормально\.

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

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

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

Думаю, вы согласитесь, что код выглядит подозрительно\. Зачем так писать? Быть может где\-то забыли изменить значение переменной var? Код подозрителен, и вы не удивитесь, что он не понравится и анализатору\. Статический анализатор PVS\-Studio выдаст предупреждение "A part of conditional expression is always false: \!var"\. Обоснованное предупреждение? Да\.

Но на практике, как всегда, есть нюансы\. Вот идентичный по смыслу код из GTK 4, но ничего подозрительного и опасного в нём уже нет:

```cpp
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\] A part of conditional expression is always false: \!debug\_enabled\. gdk\.c 281

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

К счастью, в этом ничего страшного нет\. Как уже было указано в предыдущей [статье](https://pvs-studio.ru/ru/blog/posts/cpp/0789/), есть масса способов избавиться от этих лишних срабатываний: изменить код, точечно подавить предупреждения, размечать макросы, использовать базу для массового подавления предупреждений и так далее\.

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

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

### Опечатки

**Фрагмент N1\. Красивая опечатка в цикле**

```cpp
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\] It is likely that a wrong variable is being incremented inside the 'for' operator\. Consider reviewing 'i'\. gskvulkanimage\.c 721

Обратите внимание, что во вложенном цикле инкрементируется не переменная _r_, а _i_\. Что\-то дополнительно пояснять здесь не требуется\. Это золотая классика\!

**Фрагмент N2\. Цикл который не выполняется**

В предыдущем случае в функции мог начать выполняться цикл с неконтролируемом количеством итераций\. И всё бы закончилось, когда функция _memcpy_ записала бы что\-то ну совсем не туда, куда можно\. Т\.е\. всё бы закончилось [Segmentation fault](https://en.wikipedia.org/wiki/Segmentation_fault)\.

А здесь, наоборот второй цикл вообще не будет выполняться:

```cpp
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\] Consider inspecting the 'for' operator\. It's possible that the loop will be executed incorrectly or won't be executed at all\. gtkcssbordervalue\.c 221

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

**Фрагмент N3\. Повторное использование одной и той же константы**

```cpp
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 There are identical sub\-expressions 'G\_PARAM\_EXPLICIT\_NOTIFY' to the left and to the right of the '\|' operator\. gtklistbase\.c 1151

Для генерации маски дважды используется константа _G\_PARAM\_EXPLICIT\_NOTIFY_\. Программист явно задумывал использовать разные константы, и маске в итоге выставлены не все нужные биты\.

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

Для начала взглянем на объявление функции _post\_insert\_fixup_\. Обратите внимание на порядок формальных аргументов _char\_count\_delta_ и _line\_count\_delta_\.

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

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

```cpp
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\] Possible incorrect order of arguments passed to 'post\_insert\_fixup' function: 'line\_count\_delta' and 'char\_count\_delta'\. gtktextbtree\.c 1230

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

В PVS\-Studio достаточно много эмпирических диагностик, которые выявляют абсолютно корректный код с точки зрения компиляции, но абсурдные по своей сути\. Использование анализатора позволяет выявить эти ошибки на самом раннем этапе и позволить команде при обзоре кода сосредоточиться на более высокоуровневых проблемах, а не выискивать опечатки\. Поэтому пока вы читаете это статью, предлагаю параллельно [скачать](https://pvs-studio.ru/ru/pvs-studio/download/) дистрибутив и запросить демонстрационный ключ\.

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

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

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

Неудачный вызов функции, которая была приведена выше:

```cpp
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\] Possible incorrect order of arguments passed to 'translate\_keysym' function: 'level' and 'effective\_group'\. gdkkeys\-x11\.c 1386

В этот раз что\-то напутано в работе с клавиатурой\. Вновь опечатка: перепутаны местами фактические аргументы _level_ и _effective\_group_\.

Если кто\-то пока не решился [скачать и попробовать PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/download/), то сейчас самое время :\)\. Неужели вам действительно нравится выискивать подобные ошибки глазами? Или, не дай бог, сражаться с ними в отладчике?

**Фрагмент N6\. Забыли звёздочку \(\*\)**

```cpp
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\] A part of conditional expression is always true: seq\_index\. gtkimcontextsimple\.c 475

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

```cpp
if (!seq_index)
  return FALSE;

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

**Фрагмент N7\-N9\. Повторные присваивания**

```cpp
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\] The 'priv\-\>has\_primary\_markup' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 262, 264\. gtkmessagedialog\.c 264
* V519 \[CWE\-563\] The 'priv\-\>has\_secondary\_text' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 263, 265\. gtkmessagedialog\.c 265

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

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

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

Есть ещё пара таких же бессмысленных повторных присваиваний:

* V519 \[CWE\-563\] The 'self\-\>state' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 2851, 2855\. gdkevents\.c 2855
* V519 \[CWE\-563\] The 'display\-\>width' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 2635, 2640\. gtktextlayout\.c 2640

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

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

```cpp
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\] The 'iw' pointer was utilized before it was verified against nullptr\. Check lines: 194, 199\. inspect\-button\.c 194

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

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

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

```cpp
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
                             });
```

Впрочем, такой правки окажется всё равно недостаточно :\)\. Если присмотреться, то можно увидеть ещё одно разыменование при вычислении аргументов:

```cpp
GTK_HIGHLIGHT_OVERLAY (iw->flash_overlay)
```

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

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

```cpp
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\] The 'dispatch\-\>backend' pointer was utilized before it was verified against nullptr\. Check lines: 1603, 1613\. gtkprintbackendcups\.c 1603

Это ещё более "бесстрашный" код :\)\. Указатель _dispatch\-\>backend_ разыменовывается несколько раз \(см\. участки кода, выделенные комментариями\)\. И только намного позже вспоминают, что указатель\-то оказывается может быть нулевым\! И делается соответствующая проверка:

```cpp
if (dispatch->backend)
```

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

**Фрагмент N12\. Нет защиты, если оба указателя будут нулевыми**

```cpp
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\] The 'bottom\_node' pointer was utilized before it was verified against nullptr\. Check lines: 1189, 1190\. gtksnapshot\.c 1189

Предполагается, что указатели _top\_node_ и _bottom\_node_ не могут быть одновременно нулевыми\. Это подтверждается наличием строчки:

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

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

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

### Неудачные или лишние проверки

**Фрагмент N13\. Лишняя проверка**

```cpp
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\] Expression 'desktop\_startup\_id' is always true\. gdk\.c 176

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

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

```cpp
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\. Переменная не изменяет своё значение**

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

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

```cpp
#define MAX_LIST_SIZE 1000

static void
gtk_recent_manager_real_changed (GtkRecentManager *manager)
{
  ....
  int age;
  int max_size = MAX_LIST_SIZE;
  ....
  .... // Переменная max_size не изменяется здесь.
  ....
  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\] Expression 'max\_size \> 0' is always true\. gtkrecentmanager\.c 480

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

**Примечание\.** Внимательный читатель задал вопрос: а почему нет предупреждение на часть подвыражения "max\_size \=\= 0"? Оно есть\. Просто я его пропустил при беглом просмотре отчёта и потом при написании статьи тоже не обратил на этот момент внимание\. Вот это предупреждение: V560 \[CWE\-570\] A part of conditional expression is always false: max\_size \=\= 0\. gtkrecentmanager\.c 470\.

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

```cpp
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\] The value of the 'idx' variable is checked after it was used\. Perhaps there is a mistake in program logic\. Check lines: 71, 73\. gtkatspiaction\.c 71

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

```cpp
const Action *action = &actions[idx];
```

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

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

Естественно, проверка выполнена слишком поздно, и уже произошел выход за границу массива, что приводит к неопределённому поведению программы\.

Аналогичная ситуация: V781 \[CWE\-129\] The value of the 'idx' variable is checked after it was used\. Perhaps there is a mistake in program logic\. Check lines: 132, 134\. gtkatspiaction\.c 132

**Фрагмент N17, N18\. Недостижимый код**

```cpp
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\] Unreachable code detected\. It is possible that an error is present\. gtkcssselector\.c 1077

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

Ещё одна схожая ситуация с недостижимым кодом: V779 \[CWE\-561\] Unreachable code detected\. It is possible that an error is present\. gtktreemodelfilter\.c 3289

### Разное

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

```cpp
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\] The 'width / 2' expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. gtkcellrendererspinner\.c 412
* V636 \[CWE\-682\] The 'height / 2' expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. gtkcellrendererspinner\.c 413

Результаты целочисленных делений записываются в переменные _dx_ и _dy_\. Подозрительно, что эти переменные имеют тип _double_\. Скорее всего, это просто оплошность и на самом деле должно быть написано так:

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

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

* V636 \[CWE\-682\] The 'width / 2' expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. gtkswitch\.c 255
* V636 \[CWE\-682\] The 'width / 2' expression was implicitly cast from 'int' type to 'double' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. gtkswitch\.c 257

**Фрагмент N21\. Пароль может не затираться в памяти**

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

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

1. [CWE\-14: Compiler Removal of Code to Clear Buffers](https://cwe.mitre.org/data/definitions/14.html)
1. [Безопасная очистка приватных данных](https://pvs-studio.ru/ru/blog/posts/cpp/0388/)

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

Итак, для освобождения памяти в GTK 4 используется функция _g\_free_\. Она реализована следующим образом:

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

Всегда ли _g\_free_ является просто оберткой над _free_? Начиная с GLib 2\.46 это всегда так\. Вот что сказано на эту тему в документации:

> It's important to match g\_malloc\(\) \(and wrappers such as g\_new\(\)\) with g\_free\(\), g\_slice\_alloc\(\) \(and wrappers such as g\_slice\_new\(\)\) with g\_slice\_free\(\), plain malloc\(\) with free\(\), and \(if you're using C\+\+\) new with delete and new\[\] with delete\[\]\. Otherwise bad things can happen, since these allocators may use different memory pools \(and new/delete call constructors and destructors\)\.
>
> **Since GLib 2\.46 g\_malloc\(\) is hardcoded to always use the system malloc implementation\.**

Итак, раз память в _g\_malloc_ выделятся с помощью _malloc_, то для её освобождения должна всегда вызываться функция _free_\.

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

```cpp
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\] The compiler could delete the 'memset' function call, which is used to flush 'password' object\. The memset\_s\(\) function should be used to erase the private data\. gtkprintbackendcups\.c 848

После заполнения памяти нулями, указатель на эту область памяти передаётся в функцию _g\_free_\. И здесь, как никогда, проявит себя ошибка или нет, зависит от компилятора и используемых настроек оптимизации\. Если компилятор выполнит межпроцедурную оптимизацию и вставит тело функции _g\_free_ в функцию _overwrite\_and\_free_, то он может сообразить, что функция _memset_ лишняя и удалить её\.

Вот такая интересная неприятная ошибка из сферы информационной безопасности\.

## Заключение

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

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

Поэтому мы думали и придумали выкладывать короткие видеоруководства по отдельным аспектам использования PVS\-Studio\. Так их, возможно, будет легче найти, быстро посмотреть и понять, что к чему\. И что ещё важнее, можно подписаться на канал и знакомиться с новшествами постепенно, просматривая наши новые видеосоветы после их публикации\. Лучше знакомиться с PVS\-Studio постепенно, а не сразу пытаться осилить документацию :\)\. Конечно, нет уверенности, что всё это хорошо получится, но давайте попробуем\! Приглашаю подписаться: [Возможности PVS\-Studio \(YouTube\)](https://www.youtube.com/playlist?list=PLmGioT8z4uoZsce6HQTIDFar_2hdbqPD7)\.