﻿# Статический анализ кода коллекции библиотек PMDK от Intel и ошибки, которые не ошибки

Нам предложили проверить с помощью анализатора PVS\-Studio коллекцию открытых библиотек PMDK, предназначенную для разработки и отладки приложений с поддержкой энергонезависимой памяти\. Собственно, почему бы и нет\. Тем более это небольшой проект на языке C и C\+\+ с общим размером кодовой базы около 170 KLOC, если не считать комментарии\. А значит, обзор результатов анализа не займёт много сил и времени\. Let's go\.

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

Для анализа исходного кода будет использован инструмент PVS\-Studio версии 7\.08\. Читатели нашего блога, естественно, давно знакомы с нашим инструментом, и я не буду на нём останавливаться\. Для тех, кто впервые зашёл к нам, предлагаю обратиться к статье "[Как быстро посмотреть интересные предупреждения, которые выдает анализатор PVS\-Studio для C и C\+\+ кода?](https://pvs-studio.ru/ru/blog/posts/cpp/0633/)" и [попробовать](https://pvs-studio.ru/ru/pvs-studio/download/) бесплатную триальную версию анализатора\.

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

Итак, PMDK — это коллекция библиотек и инструментальных средств с открытым исходным кодом, предназначенных для упрощения разработки, отладки и управления приложениями с поддержкой энергонезависимой памяти\. Подробнее здесь: [PMDK Introduction](https://docs.pmem.io/persistent-memory/getting-started-guide/what-is-pmdk)\. Исходники здесь: [pmdk](https://github.com/pmem/pmdk)\.

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

## Неправильный код, который работает

### Размер выделяемой памяти

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

```cpp
int main(int argc, char *argv[])
{
  ....
  struct pool *pop = malloc(sizeof(pop));
  ....
}
```

Предупреждение PVS\-Studio: V568 It's odd that 'sizeof\(\)' operator evaluates the size of a pointer to a class, but not the size of the 'pop' class object\. util\_ctl\.c 717

Классическая опечатка, из\-за которой выделяется неверное количество памяти\. Оператор _sizeof_ вернёт не размер структуры, а размер указателя на эту структуру\. Правильным вариантом будет:

```cpp
struct pool *pop = malloc(sizeof(pool));
```

или

```cpp
struct pool *pop = malloc(sizeof(*pop));
```

Однако, этот неправильно написанный код прекрасно работает\. Дело в том, что структура _pool_ содержит в себе ровно один указатель:

```cpp
struct pool {
  struct ctl *ctl;
};
```

Получается, что структура занимает ровно столько, сколько и указатель\. Всё хорошо\.

### Длина строки

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

```cpp
typedef void *(*pmem2_memcpy_fn)(void *pmemdest, const void *src, size_t len,
    unsigned flags);

static const char *initial_state = "No code.";

static int
test_rwx_prot_map_priv_do_execute(const struct test_case *tc,
  int argc, char *argv[])
{
  ....
  char *addr_map = pmem2_map_get_address(map);
  map->memcpy_fn(addr_map, initial_state, sizeof(initial_state), 0);
  ....
}
```

Предупреждение PVS\-Studio: V579 \[CWE\-687\] The memcpy\_fn function receives the pointer and its size as arguments\. It is possibly a mistake\. Inspect the third argument\. pmem2\_map\_prot\.c 513

Для копирования строки используется указатель на специальную функцию копирования\. Обратите внимание на вызов этой функции, а вернее на её третий аргумент\.

Программист предполагает, что оператор _sizeof_ вычислит размер строкового литерала\. Но, на самом деле, вновь вычисляется размер указателя\.

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

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

**Сценарий 1\.** Нужно было скопировать терминальный ноль\. Тогда я неправ, и это вовсе не безобидная ошибка, которая себя не проявляет\. Скопировано не 9 байт, а только 8\. Терминального нуля нет, и последствия предсказать невозможно\. В этом случае код можно исправить, изменив определение константной строки _initial\_state_ следующим образом:

```cpp
static const char initial_state [] = "No code.";
```

Теперь значение _sizeof\(initial\_state\)_ равно 9\.

**Сценарий 2\.** Терминальный ноль вовсе и не требуется\. Например, ниже можно увидеть вот такую строчку кода:

```cpp
UT_ASSERTeq(memcmp(addr_map, initial_state, strlen(initial_state)), 0);
```

Как видите, функция _strlen_ вернёт значение 8 и терминальный ноль не участвует в сравнении\. Тогда это действительно везение и всё хорошо\.

### Побитовый сдвиг

Следующий пример связан с операцией побитового сдвига\.

```cpp
static int
clo_parse_single_uint(struct benchmark_clo *clo, const char *arg, void *ptr)
{
  ....
  uint64_t tmax = ~0 >> (64 - 8 * clo->type_uint.size);
  ....
}
```

Предупреждение PVS\-Studio: V610 \[CWE\-758\] Unspecified behavior\. Check the shift operator '\>\>'\. The left operand '\~0' is negative\. clo\.cpp 205

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

### Приоритет операций

И рассмотрим последний случай, связанный с приоритетом операций\.

```cpp
#define BTT_CREATE_DEF_SIZE  (20 * 1UL << 20) /* 20 MB */
```

Предупреждение PVS\-Studio: V634 \[CWE\-783\] The priority of the '\*' operation is higher than that of the '<<' operation\. It's possible that parentheses should be used in the expression\. bttcreate\.c 204

Чтобы получить константу, равную значению 20 MB, программист решил выполнить следующие действия:

1. Сдвинул 1 на 20 разрядов, чтобы получить значение 1048576, т\.е\. 1 MB\.
1. Умножил 1 MB на 20\.

Другими словами, программист думает, что вычисления происходят так: \(20 \* \(1UL << 20\)\)

Но на самом деле приоритет оператора умножения выше, чем приоритет оператора сдвига и выражение вычисляется так: \(\(20 \* 1UL\) << 20\)\.

Согласитесь, вряд ли программист хотел, чтобы выражение вычислилось в такой последовательности\. Нет смысла в умножении 20 на 1\. Так что перед нами тот случай, когда код работает не так, как задумывал программист\.

Но эта ошибка никак не проявит себя\. Неважно, как написать:

* \(20 \* 1UL << 20\)
* \(20 \* \(1UL << 20\)\)
* \(\(20 \* 1UL\) << 20\)

Результат всё равно [всегда одинаковый](https://godbolt.org/z/sPsoMT)\! Всегда получается нужное значение 20971520 и программа работает совершенно корректно\.

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

### Не там поставленная скобка

```cpp
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004

static void
enum_handles(int op)
{
  ....
  NTSTATUS status;
  while ((status = NtQuerySystemInformation(
      SystemExtendedHandleInformation,
      hndl_info, hi_size, &req_size)
        == STATUS_INFO_LENGTH_MISMATCH)) {
    hi_size = req_size + 4096;
    hndl_info = (PSYSTEM_HANDLE_INFORMATION_EX)REALLOC(hndl_info,
        hi_size);
  }
  UT_ASSERT(status >= 0);
  ....
}
```

Предупреждение PVS\-Studio: V593 \[CWE\-783\] Consider reviewing the expression of the 'A \= B \=\= C' kind\. The expression is calculated as following: 'A \= \(B \=\= C\)'\. ut\.c 641

Внимательно посмотрите вот сюда:

```cpp
while ((status = NtQuerySystemInformation(....) == STATUS_INFO_LENGTH_MISMATCH))
```

Программист хотел сохранить в переменной _status_ значение, которое возвращает функцию _NtQuerySystemInformation_, а затем сравнить его с константой\.

Программист скорее всего знал, что приоритет оператора сравнения \(\=\=\) выше, чем у оператора присваивания \(\=\), и поэтому следует использовать скобки\. Но опечатался и поставил их не там, где надо\. В результате скобки никак не помогают\. Корректный код:

```cpp
while ((status = NtQuerySystemInformation(....)) == STATUS_INFO_LENGTH_MISMATCH)
```

Из\-за этой ошибки, макрос _UT\_ASSERT_ никогда не сработает\. Ведь в переменную _status_ всегда заносится результат сравнения, то есть ложь \(0\) или истина \(1\)\. Значит условие \(\[0\.\.1\] \>\= 0\) всегда истинно\.

### Потенциальная утечка памяти

```cpp
static enum pocli_ret
pocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp)
{
  char *input = strdup(in);
  if (!input)
    return POCLI_ERR_MALLOC;

  if (!oidp)
    return POCLI_ERR_PARS;
  ....
}
```

Предупреждение PVS\-Studio: V773 \[CWE\-401\] The function was exited without releasing the 'input' pointer\. A memory leak is possible\. pmemobjcli\.c 238

Если _oidp_ окажется нулевым указателем, то будет потеряна копия строки, созданная с помощью вызова функции _strdup_\. Лучше всего будет перенести проверку до выделения памяти:

```cpp
static enum pocli_ret
pocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp)
{
  if (!oidp)
    return POCLI_ERR_PARS;

  char *input = strdup(in);
  if (!input)
    return POCLI_ERR_MALLOC;
  ....
}
```

Или можно явно освобождать память:

```cpp
static enum pocli_ret
pocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp)
{
  char *input = strdup(in);
  if (!input)
    return POCLI_ERR_MALLOC;

  if (!oidp)
  {
    free(input);
    return POCLI_ERR_PARS;
  }
  ....
}
```

### Потенциальное переполнение

```cpp
typedef long long os_off_t;

void
do_memcpy(...., int dest_off, ....., size_t mapped_len, .....)
{
  ....
  LSEEK(fd, (os_off_t)(dest_off + (int)(mapped_len / 2)), SEEK_SET);
  ....
}
```

Предупреждение PVS\-Studio: V1028 \[CWE\-190\] Possible overflow\. Consider casting operands, not the result\. memcpy\_common\.c 62

Явное приведение результата сложения к типу _os\_off\_t_ не имеет смысла\. Во\-первых, это не защищает от потенциального переполнения, которое может возникнуть при сложении двух значений типа _int_\. Во\-вторых, результат сложения и так бы отлично неявно расширился до типа _os\_off\_t_\. Явное приведение типа просто избыточно\.

Думаю, правильнее будет написать так:

```cpp
LSEEK(fd, dest_off + (os_off_t)(mapped_len) / 2, SEEK_SET);
```

Здесь беззнаковое значение типа _size\_t_ превращается в знаковое \(чтоб не было какого\-нибудь предупреждения от компилятора\)\. И заодно точно не возникнет переполнение при сложении\.

### Неправильная защита от переполнения

```cpp
static DWORD
get_rel_wait(const struct timespec *abstime)
{
  struct __timeb64 t;
  _ftime64_s(&t);
  time_t now_ms = t.time * 1000 + t.millitm;
  time_t ms = (time_t)(abstime->tv_sec * 1000 +
    abstime->tv_nsec / 1000000);

  DWORD rel_wait = (DWORD)(ms - now_ms);

  return rel_wait < 0 ? 0 : rel_wait;
}
```

Предупреждение PVS\-Studio: V547 \[CWE\-570\] Expression 'rel\_wait < 0' is always false\. Unsigned type value is never < 0\. os\_thread\_windows\.c 359

Мне не очень понятно, от какой именно ситуации должна защищать проверка, но она в любом случае не работает\. Переменная _rel\_wait_ имеет беззнаковый тип _DWORD_\. А значит, сравнение _rel\_wait < 0_ не имеет смысла, так как результатом всегда является ложь\.

### Отсутствие проверки, что память успешно выделена

Проверка того, что память выделена, осуществляется с помощью макросов _assert_, которые ничего не делают, если компилируется Release версия приложения\. Так что можно сказать, что нет никакой обработки ситуации, когда функции _malloc_ возвращают _NULL_\. Пример:

```cpp
static void
remove_extra_node(TOID(struct tree_map_node) *node)
{
  ....
  unsigned char *new_key = (unsigned char *)malloc(new_key_size);
  assert(new_key != NULL);
  memcpy(new_key, D_RO(tmp)->key, D_RO(tmp)->key_size);
  ....
}
```

Предупреждение PVS\-Studio: V575 \[CWE\-628\] The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 340, 338\. rtree\_map\.c 340

В других местах нет даже _assert_:

```cpp
static void
calc_pi_mt(void)
{
  ....
  HANDLE *workers = (HANDLE *) malloc(sizeof(HANDLE) * pending);
  for (i = 0; i < pending; ++i) {
    workers[i] = CreateThread(NULL, 0, calc_pi,
      &tasks[i], 0, NULL);
    if (workers[i] == NULL)
      break;
  }
  ....
}
```

Предупреждение PVS\-Studio: V522 \[CWE\-690\] There might be dereferencing of a potential null pointer 'workers'\. Check lines: 126, 124\. pi\.c 126

Таких фрагментов кода я насчитал минимум 37 штук\. Так что я не вижу смысла перечислять все их в статье\.

На первый взгляд, отсутствие проверок можно посчитать просто неаккуратностью и сказать, что это код с запахом\. Я не согласен с такой позицией\. Программисты недооценивают опасность отсутствия таких проверок\. Нулевой указатель вовсе не обязательно сразу проявит себя падением программы при попытке его разыменования\. Последствия могут быть более причудливыми и опасными, особенно в многопоточных программах\. Чтобы подробнее понять, в чём дело и почему проверки нужны, настоятельно рекомендую всем познакомиться со статьёй "[Почему важно проверять, что вернула функция malloc](https://pvs-studio.ru/ru/blog/posts/cpp/0558/)"\.

## Код с запахом

### Двойной вызов CloseHandle

```cpp
static void
prepare_map(struct pmem2_map **map_ptr,
  struct pmem2_config *cfg, struct pmem2_source *src)
{
  ....
  HANDLE mh = CreateFileMapping(....);
  ....
  UT_ASSERTne(CloseHandle(mh), 0);
  ....
}
```

Предупреждение PVS\-Studio: V586 \[CWE\-675\] The 'CloseHandle' function is called twice for deallocation of the same resource\. pmem2\_map\.c 76

Смотря на этот код и предупреждение PVS\-Studio понятно, что ничего непонятно\. Где тут возможен повторный вызов _CloseHandle_? Чтобы найти ответ, давайте посмотрим на реализацию макроса _UT\_ASSERTne_\.

```cpp
#define UT_ASSERTne(lhs, rhs)\
  do {\
    /* See comment in UT_ASSERT. */\
    if (__builtin_constant_p(lhs) && __builtin_constant_p(rhs))\
      UT_ASSERT_COMPILE_ERROR_ON((lhs) != (rhs));\
    UT_ASSERTne_rt(lhs, rhs);\
  } while (0)
```

Сильно понятнее не стало\. Что такое _UT\_ASSERT\_COMPILE\_ERROR\_ON_? Что _такое UT\_ASSERTne\_rt_?

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

```cpp
do {
  if (0 && 0) (void)((CloseHandle(mh)) != (0));
  ((void)(((CloseHandle(mh)) != (0)) ||
    (ut_fatal(".....", 76, __FUNCTION__, "......: %s (0x%llx) != %s (0x%llx)",
              "CloseHandle(mh)", (unsigned long long)(CloseHandle(mh)), "0",
              (unsigned long long)(0)), 0))); } while (0);
```

Удалим всегда ложное условие \(0 && 0\) и, вообще, всё не относящееся к делу\. Получается:

```cpp
((void)(((CloseHandle(mh)) != (0)) ||
  (ut_fatal(...., "assertion failure: %s (0x%llx) != %s (0x%llx)",
            ....., (unsigned long long)(CloseHandle(mh)), .... ), 0)));
```

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

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

### Несоответствие интерфейса реализации \(снятие константности\)

```cpp
static int
status_push(PMEMpoolcheck *ppc, struct check_status *st, uint32_t question)
{
  ....
  } else {
    status_msg_info_and_question(st->msg);            // <=
    st->question = question;
    ppc->result = CHECK_RESULT_ASK_QUESTIONS;
    st->answer = PMEMPOOL_CHECK_ANSWER_EMPTY;
    PMDK_TAILQ_INSERT_TAIL(&ppc->data->questions, st, next);
  }
  ....
}
```

Анализатор выдаёт сообщение: V530 \[CWE\-252\] The return value of function 'status\_msg\_info\_and\_question' is required to be utilized\. check\_util\.c 293

Причина в том, что функция _status\_msg\_info\_and\_question_, с точки зрения анализатора, не меняет состояние внешних по отношению к ней объектов, в том числе переданную константную строчку\. Т\.е\. функция просто что\-то считает и возвращает результат\. А раз так, то странно не использовать результат, который возвращает эта функция\. И, хотя анализатор в этот раз не прав, он указывает на код с запахом\. Давайте посмотрим, как устроена вызываемая функция _status\_msg\_info\_and\_question_\.

```cpp
static inline int
status_msg_info_and_question(const char *msg)
{
  char *sep = strchr(msg, MSG_SEPARATOR);
  if (sep) {
    *sep = ' ';
    return 0;
  }
  return -1;
}
```

При вызове функции _strchr_ происходит неявное снятие константности\. Дело в том, что в C она объявлена так:

```cpp
char * strchr ( const char *, int );
```

Не лучшее решение\. Но язык C такой, какой есть :\)\.

Анализатор запутался и не понял, что переданная строка на самом деле изменяется\. А раз так, то возвращаемое значения не самое главное и его можно не использовать\.

Тем не менее, хоть анализатор и запутался, он указывает на код с запахом\. То, что сбивает с толку анализатор, может сбивать с толку и человека, который сопровождает код\. Лучше было бы объявить функцию более честно, убрав _const_:

```cpp
static inline int
status_msg_info_and_question(char *msg)
{
  char *sep = strchr(msg, MSG_SEPARATOR);
  if (sep) {
    *sep = ' ';
    return 0;
  }
  return -1;
}
```

Так и намерения сразу понятнее, и анализатор будет молчать\.

### Переусложненный код

```cpp
static struct memory_block
heap_coalesce(struct palloc_heap *heap,
  const struct memory_block *blocks[], int n)
{
  struct memory_block ret = MEMORY_BLOCK_NONE;

  const struct memory_block *b = NULL;
  ret.size_idx = 0;
  for (int i = 0; i < n; ++i) {
    if (blocks[i] == NULL)
      continue;
    b = b ? b : blocks[i];
    ret.size_idx += blocks[i] ? blocks[i]->size_idx : 0;
  }
  ....
}
```

Предупреждение PVS\-Studio: V547 \[CWE\-571\] Expression 'blocks\[i\]' is always true\. heap\.c 1054

Если _blocks\[i\] \=\= NULL_, то сработает оператор _continue_ и цикл начнёт следующую итерацию\. Поэтому повторная проверка элемента _blocks\[i\]_ не имеет смысла и тернарный оператор является лишним\. Код можно упростить:

```cpp
....
for (int i = 0; i < n; ++i) {
  if (blocks[i] == NULL)
    continue;
  b = b ? b : blocks[i];
  ret.size_idx += blocks[i]->size_idx;
}
....
```

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

```cpp
void win_mmap_fini(void)
{
  ....
  if (mt->BaseAddress != NULL)
    UnmapViewOfFile(mt->BaseAddress);
  size_t release_size =
    (char *)mt->EndAddress - (char *)mt->BaseAddress;
  void *release_addr = (char *)mt->BaseAddress + mt->FileLen;
  mmap_unreserve(release_addr, release_size - mt->FileLen);
  ....
}
```

Предупреждение PVS\-Studio: V1004 \[CWE\-119\] The '\(char \*\) mt\-\>BaseAddress' pointer was used unsafely after it was verified against nullptr\. Check lines: 226, 235\. win\_mmap\.c 235

Указатель _mt\-\>BaseAddress_ может быть нулевым, о чём свидетельствует проверка:

```cpp
if (mt->BaseAddress != NULL)
```

Однако ниже этот указатель уже используется в арифметических действиях без проверки\. Например, здесь:

```cpp
size_t release_size =
  (char *)mt->EndAddress - (char *)mt->BaseAddress;
```

Будет получено некое большое целочисленное значение, равное фактически значению указателя _mt\-\>EndAddress_\. Возможно, это и не ошибка, но выглядит всё это очень подозрительно, и мне кажется, код следует перепроверить\. Запах заключается в том, что код непонятен и ему явно не хватает поясняющих комментариев\.

### Короткие имена глобальных переменных

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

```cpp
static struct critnib *c;
```

Предупреждения PVS\-Studio на такие переменные:

* V707 Giving short names to global variables is considered to be bad practice\. It is suggested to rename 'ri' variable\. map\.c 131
* V707 Giving short names to global variables is considered to be bad practice\. It is suggested to rename 'c' variable\. obj\_critnib\_mt\.c 56
* V707 Giving short names to global variables is considered to be bad practice\. It is suggested to rename 'Id' variable\. obj\_list\.h 68
* V707 Giving short names to global variables is considered to be bad practice\. It is suggested to rename 'Id' variable\. obj\_list\.c 34

## Странное

![0756_PMDK_Check_ru/image2.png](https://import.viva64.com/docx/blog/0756_PMDK_Check_ru/image2.png)

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

```cpp
void
do_memmove(char *dst, char *src, const char *file_name,
    size_t dest_off, size_t src_off, size_t bytes,
    memmove_fn fn, unsigned flags, persist_fn persist)
{
  ....
  /* do the same using regular memmove and verify that buffers match */
  memmove(dstshadow + dest_off, dstshadow + dest_off, bytes / 2);
  verify_contents(file_name, 0, dstshadow, dst, bytes);
  verify_contents(file_name, 1, srcshadow, src, bytes);
  ....
}
```

Предупреждение PVS\-Studio: V549 \[CWE\-688\] The first argument of 'memmove' function is equal to the second argument\. memmove\_common\.c 71

Обратите внимание, что первый и второй аргумент функции совпадают\. Таким образом, функция по факту ничего не делает\. Какие мне на ум приходят варианты:

1. Хотелось "потрогать" блок памяти\. Но произойдёт ли это в реальности? Не удалит ли оптимизирующий компилятор код, который копирует блок памяти сам в себя?
1. Это какой\-то юнит\-тест на работу функции _memmove_\.
1. Код содержит опечатку\.

А вот не менее странный фрагмент в этой же функции:

```cpp
void
do_memmove(char *dst, char *src, const char *file_name,
    size_t dest_off, size_t src_off, size_t bytes,
    memmove_fn fn, unsigned flags, persist_fn persist)
{
  ....
  /* do the same using regular memmove and verify that buffers match */
  memmove(dstshadow + dest_off, srcshadow + src_off, 0);
  verify_contents(file_name, 2, dstshadow, dst, bytes);
  verify_contents(file_name, 3, srcshadow, src, bytes);
  ....
}
```

Предупреждение PVS\-Studio: V575 \[CWE\-628\] The 'memmove' function processes '0' elements\. Inspect the third argument\. memmove\_common\.c 82

Функция перемещает 0 байт\. Что это? Юнит\-тест? Опечатка?

Для меня этот код непонятен и странен\.

## Зачем использовать анализаторы кода?

Может показаться, что раз найдено мало ошибок, то и внедрение анализатора в процесс разработки кода малообоснованно\. Но смысл использования инструментов статического анализа не в разовых проверках, а в регулярном выявлении ошибок ещё на этапе написания кода\. В противном случае, эти ошибки выявляются более дорогими и медленными способами \(отладка, тестирование, отзывы пользователей и так далее\)\. Более подробно эта мысль изложена в статье "[Ошибки, которые не находит статический анализ кода, потому что он не используется](https://pvs-studio.ru/ru/blog/posts/cpp/0639/)",  с которой я рекомендую познакомиться\. А затем приходите к нам на сайт [скачать](https://pvs-studio.ru/ru/pvs-studio/download/) и попробовать PVS\-Studio для проверки своих проектов\.

Спасибо за внимание\!