﻿# Исследуем качество кода операционной системы Zephyr

Недавно мы рассказывали, что анализатор кода PVS\-Studio начал интегрироваться с PlatformIO\. Естественно, при этом команда разработчиков PVS\-Studio общалась с командой PlatformIO и те предложили ради интереса проверить код операционной системы реального времени Zephyr\. Почему бы и нет, подумали мы, и вот перед вами статья о таком исследовании\.

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


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

## PlatformIO

Прежде чем приступить к основной части статьи, хочется отрекомендовать разработчикам встраиваемых систем проект [PlatformIO](https://platformio.org/), который может облегчить им жизнь\. Это кроссплатформенный инструмент для программирования микроконтроллеров\. Ядром PlatformIO является инструмент с интерфейсом командной строки, однако рекомендуется использовать его в виде плагина для Visual Studio Code\. Поддерживается большое количество современных чипов и плат на их основе\. Умеет автоматически загружать подходящие системы сборки, а на сайте собрана большая коллекция библиотек для управления подключаемыми электронными компонентами\. Есть поддержка нескольких статических анализаторов кода, в том числе и PVS\-Studio\.

## PVS\-Studio

PVS\-Studio пока мало известен в мире встраиваемых систем, поэтому я на всякий случай сделаю вступление для новых читателей, пока ещё не знакомых с этим инструментом\. Наши постоянные читатели могут сразу перейти к следующему разделу\.

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

* Windows\. Visual Studio 2010\-2019 C, C\+\+, C\+\+/CLI, C\+\+/CX \(WinRT\)
* Windows\. IAR Embedded Workbench, C/C\+\+ Compiler for ARM C, C\+\+
* Windows\. QNX Momentics, QCC C, C\+\+
* Windows/Linux\. Keil µVision, DS\-MDK, ARM Compiler 5/6 C, C\+\+
* Windows/Linux\. Texas Instruments Code Composer Studio, ARM Code Generation Tools C, C\+\+
* Windows/Linux/macOS\. GNU Arm Embedded Toolchain, Arm Embedded GCC compiler, C, C\+\+
* Windows/Linux/macOS\. Clang C, C\+\+
* Linux/macOS\. GCC C, C\+\+
* Windows\. MinGW C, C\+\+

Анализатор имеет свою собственную систему классификации [предупреждений](https://pvs-studio.ru/ru/docs/warnings/), но в случае необходимости вы можете включить отображение предупреждений согласно стандартам кодирования [CWE](https://pvs-studio.ru/ru/docs/warnings/), [SEI CERT](https://pvs-studio.ru/ru/pvs-studio/sast/cert/), [MISRA](https://pvs-studio.ru/ru/pvs-studio/sast/misra/)\.

Вы можете быстро начать регулярно использовать PVS\-Studio даже в большом legacy\-проекте\. Для этого предусмотрен специальный механизм массового [подавления предупреждений](https://pvs-studio.ru/ru/docs/manual/0032/)\. Все текущие предупреждения считаются техническим долгом и прячутся, что позволяет сосредоточиться на предупреждениях, относящихся только к новому или модифицированному коду\. Это позволяет команде сразу начать ежедневно использовать анализатор в своей работе, а к техническому долгу можно время от времени возвращаться и улучшать код\.

Существует множество других сценариев использования PVS\-Studio\. Например, вы можете использовать его как плагин к SonarQube\. Возможна интеграция с такими системами, как Travis CI, CircleCI, GitLab CI/CD и т\.д\. Более подробное описание PVS\-Studio выходит за рамки этой статьи\. Поэтому предлагаю ознакомиться со статьёй, в которой много полезных ссылок, и в которой даны ответы на многие вопросы: "[Причины внедрить в процесс разработки статический анализатор кода PVS\-Studio](https://pvs-studio.ru/ru/blog/posts/0687/)"\.

## Zephyr

Работая над [интеграцией PVS\-Studio в PlatformIO](https://pvs-studio.ru/ru/blog/posts/cpp/0714/), наши команды пообщались, и нам предложили проверить проект из мира embedded, а именно \- Zephyr\. Идея нам понравилась, что и послужило поводом к написанию этой статьи\.

[Zephyr](https://www.zephyrproject.org/) \- легковесная операционная система реального времени, предназначенная для работы на устройствах с ограниченными ресурсами различных архитектур\. Код распространяется под открытой лицензией Apache 2\.0\. Работает на следующих платформах: ARM \(Cortex\-M0, Cortex\-M3, Cortex\-M4, Cortex\-M23, Cortex\-M33, Cortex\-R4, Cortex\-R5, Cortex\-A53\), x86, x86\-64, ARC, RISC\-V, Nios II, Xtensa\.

Некоторые особенности:

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

Из интересных для нас моментов, в разработке операционной системы принимает участие компания [Synopsys](https://ru.wikipedia.org/wiki/Synopsys)\. В 2014 году компания Synopsys приобрела компанию Coverity, выпускавшую одноименный статический анализатор кода\.

Совершенно естественно, что с самого начала при разработке Zephyr используется анализатор [Coverity](https://en.wikipedia.org/wiki/Coverity)\. Анализатор является лидером рынка и это не могло не сказаться в лучшую сторону на качестве кода операционной системы\.

## Качество кода Zephyr

По моему мнению, код операционной системы Zephyr является качественным\. Вот что даёт повод мне так думать:

* Анализатор PVS\-Studio выдал 122 предупреждения общего назначения уровня High и 367 предупреждений уровня Medium\. Это немного, учитывая, что было проверено 560 C/C\+\+ файлов\. Ядро проверяется путём проверки сэмплов\. Всего же в проекте я насчитал 7810 C/C\+\+ файлов и 10075 заголовочных файлов\. Получается, что проверена только часть проекта\. Впрочем, задача проверить всё и не ставилась, а полученные результаты всё равно говорят о небольшой плотности предупреждений\.
* Многие из выданных предупреждений являются ложными или полуложными\. Что подразумевается под "полуложными" предупреждениями, я поясню ниже\.
* Утилита SourceMonitor, проанализировав исходный код, выдала статистику, что 48% кода является комментариями\. Это много и по моему опыту свидетельствует о высокой заботе о качестве кода, его понятности для других разработчиков\.
* При разработке проекта используется статический анализатор кода Coverity\. Скорее всего, в силу именно этого факта, анализатор PVS\-Studio хотя и нашёл ошибки в проекте, но не смог показать себя ярко, как это иногда бывает при анализе других проектов\.

На основании этого я считаю, что авторы проекта заботятся о качестве и надёжности кода\. Давайте теперь рассмотрим некоторые предупреждения, выданные анализатором PVS\-Studio \(версия 7\.06\)\. 

## "Полуложные" предупреждения

Код проекта в силу своей низкоуровневости написан достаточно специфично и с большим количеством условной компиляции \(\#ifdef\)\. Это порождает большое количество предупреждений, которые не указывают на настоящую ошибку, но при этом их нельзя назвать просто ложными\. Проще всего будет пояснить это, приведя несколько примеров\.

**Пример "полуложного" срабатывания N1**

```cpp
static struct char_framebuffer char_fb;

int cfb_framebuffer_invert(struct device *dev)
{
  struct char_framebuffer *fb = &char_fb;

  if (!fb || !fb->buf) {
    return -1;
  }

  fb->inverted = !fb->inverted;

  return 0;
}
```

Предупреждение PVS\-Studio: [V560](https://pvs-studio.ru/ru/docs/warnings/v560/) A part of conditional expression is always false: \!fb\. cfb\.c 188

При взятии адреса статической переменной всегда получается ненулевой указатель\. Поэтому указатель _fb_ всегда не равен нулю и его проверка не имеет смысла\.

Однако видно, что это никакая не ошибка, а просто избыточная проверка, которая ничем не вредит\. Более того, при построении Release версии компилятор её выбросит, так что это даже не повлечёт замедления работы\.

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

**Пример "полуложного" срабатывания N2**

```cpp
int hex2char(u8_t x, char *c)
{
  if (x <= 9) {
    *c = x + '0';
  } else if (x >= 10 && x <= 15) {
    *c = x - 10 + 'a';
  } else {
    return -EINVAL;
  }
  return 0;
}
```

Предупреждение PVS\-Studio: [V560](https://pvs-studio.ru/ru/docs/warnings/v560/) A part of conditional expression is always true: x \>\= 10\. hex\.c 31

Анализатор вновь формально прав, утверждая, что часть условия всегда истинна\. Если переменная _x_ не меньше/равна 9, то получается, что она всегда больше/равна 10\. И код можно упростить:

```cpp
} else if (x <= 15) {
```

Вновь понятно, что никакой настоящей вредной ошибки здесь нет, и лишнее сравнение написано просто для красоты кода\.

**Теперь давайте рассмотрим более сложный пример N3**

Для начала посмотрим, как может быть реализован макрос _CHECKIF_\.

```cpp
#if defined(CONFIG_ASSERT_ON_ERRORS)
#define CHECKIF(expr) \
  __ASSERT_NO_MSG(!(expr));   \
  if (0)
#elif defined(CONFIG_NO_RUNTIME_CHECKS)
#define CHECKIF(...) \
  if (0)
#else
#define CHECKIF(expr) \
  if (expr)
#endif
```

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

```cpp
#define CHECKIF(expr) \
  if (expr)
```

Посмотрим теперь, к чему это приводит\.

```cpp
int k_queue_append_list(struct k_queue *queue, void *head, void *tail)
{
  CHECKIF(head == NULL || tail == NULL) {
    return -EINVAL;
  }

  k_spinlock_key_t key = k_spin_lock(&queue->lock);
  struct k_thread *thread = NULL;
  if (head != NULL) {
    thread = z_unpend_first_thread(&queue->wait_q);
  }
  ....
}
```

Предупреждение PVS\-Studio: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) \[CWE\-571\] Expression 'head \!\= NULL' is always true\. queue\.c 244

Анализатор считает, что проверка _\(head \!\= NULL\)_ всегда даёт истину\. И это действительно так\. Если указатель _head_ был равен NULL, то функция бы прекратила свою работу благодаря проверке в начале функции:

```cpp
CHECKIF(head == NULL || tail == NULL) {
  return -EINVAL;
}
```

Напомним, что здесь макрос раскрывается так:

```cpp
if (head == NULL || tail == NULL) {
  return -EINVAL;
}
```

Итак, анализатор PVS\-Studio прав со своей точки зрения и выдаёт корректное предупреждение\. Однако удалить эту проверку нельзя\. Она нужна\. При другом сценарии макрос раскроется так:

```cpp
if (0) {
  return -EINVAL;
}
```

И тогда повторная проверка указателя нужна\. Конечно, анализатор не выдаст предупреждение в таком варианте компиляции кода\. Однако он выдаёт предупреждение для отладочного варианта компиляции\.

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

## Предупреждения по делу

А удалось ли найти всё\-таки что\-то интересное? Удалось, и сейчас мы посмотрим на различные ошибки\. При этом хочу сразу отметить два момента:

1. Суть статического анализа не в подобных разовых проверках\. Правильная методология: регулярный запуск анализатора, как, собственно, сейчас в проекте и используется Coverity\. Поэтому, добавив в процесс разработки PVS\-Studio или какой\-либо другой анализатор, можно на раннем этапе найти ещё больше ошибок и тем самым снизить стоимость их исправления\. 
1. При написании статьи у меня не было цели найти как можно больше ошибок\. Поэтому, многие ошибки остались мной незамеченными или я зря отнёс их к категории "полуложных" срабатываний и не включил в статью\. Если авторов проекта заинтересует эта публикация, я рекомендую им самостоятельно провести анализ и изучить полученный отчёт\. Так как проект является открытым и находится на GitHub, то можно воспользоваться [бесплатным вариантом лицензирования](https://pvs-studio.ru/ru/blog/posts/0614/) PVS\-Studio\.

**Фрагмент N1, опечатка**

```cpp
static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf)
{
  ....
  if (link.tx.cb && link.tx.cb) {
    link.tx.cb(0, link.tx.cb_data);
  }
  ....
}
```

Предупреждение PVS\-Studio: [V501](https://pvs-studio.ru/ru/docs/warnings/v501/) \[CWE\-571\] There are identical sub\-expressions to the left and to the right of the '&&' operator: link\.tx\.cb && link\.tx\.cb pb\_adv\.c 377

Дважды проверяется одна и та же переменная _link\.tx\.cb_\. Видимо, это опечатка, и второй проверяемой переменной должна выступать _link\.tx\.cb\_data_\.

**Фрагмент N2, выход за границу буфера**

Рассмотрим функцию _net\_hostname\_get_, которая будет использоваться дальше\.

```cpp
#if defined(CONFIG_NET_HOSTNAME_ENABLE)
const char *net_hostname_get(void);
#else
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
#endif
```

В моём случае при препроцессировании выбирался вариант, относящийся к ветке _\#else_\. То\-есть в препроцессированном файле функция реализуется так:

```cpp
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
```

Функция возвращает указатель на массив из 7 байт \(учитываем терминальный ноль в конце строки\)\.

Теперь рассмотрим код, приводящий к выходу за границу массива\.

```cpp
static int do_net_init(void)
{
  ....
  (void)memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);
  ....
}
```

Предупреждение PVS\-Studio: [V512](https://pvs-studio.ru/ru/docs/warnings/v512/) \[CWE\-119\] A call of the 'memcpy' function will lead to the 'net\_hostname\_get\(\)' buffer becoming out of range\. log\_backend\_net\.c 114

После препроцессирования _MAX\_HOSTNAME\_LEN_ раскрывается следующим образом:

```cpp
(void)memcpy(hostname, net_hostname_get(),
    sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"));
```

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

**Фрагмент N3, потенциальный выход за границу буфера**

```cpp
int do_write_op_json(struct lwm2m_message *msg)
{
  u8_t value[TOKEN_BUF_LEN];
  u8_t base_name[MAX_RESOURCE_LEN];
  u8_t full_name[MAX_RESOURCE_LEN];
  ....
  /* combine base_name + name */
  snprintf(full_name, TOKEN_BUF_LEN, "%s%s", base_name, value);
  ....
}
```

Предупреждение PVS\-Studio: [V512](https://pvs-studio.ru/ru/docs/warnings/v512/) \[CWE\-119\] A call of the 'snprintf' function will lead to overflow of the buffer 'full\_name'\. lwm2m\_rw\_json\.c 826

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

```cpp
u8_t value[64];
u8_t base_name[20];
u8_t full_name[20];
....
snprintf(full_name, 64, "%s%s", base_name, value);
```

Под буфер _full\_name_, в котором формируется строка, выделено только 20 байт\. При этом части, из которых формируется строка, хранятся в буферах размером 20 и 64 байта\. Более того, константа 64, передаваемая в функцию _snprintf_ и призванная предотвратить выход за границу массива, явно великовата\!

Этот код не обязательно приведёт к переполнению буфера\. Возможно, всегда везёт, и подстроки всегда очень маленькие\. Однако, в целом, этот код никак не защищён от переполнения и содержит классический дефект безопасности [CWE\-119](https://cwe.mitre.org/data/definitions/119.html)\.

**Фрагмент N4, выражение всегда истинно**

```cpp
static int keys_set(const char *name, size_t len_rd, settings_read_cb read_cb,
                    void *cb_arg)
{
  ....
  size_t len;
  ....
  len = read_cb(cb_arg, val, sizeof(val));
  if (len < 0) {
    BT_ERR("Failed to read value (err %zu)", len);
    return -EINVAL;
  }
  ....
}
```

Предупреждение PVS\-Studio: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) \[CWE\-570\] Expression 'len < 0' is always false\. Unsigned type value is never < 0\. keys\.c 312

Переменная _len_ имеет беззнаковый тип и, значит, не может быть меньше 0\. Соответственно, статус ошибки никак не обрабатывается\. В других местах для хранения результата работы функции _read\_cb_ используется тип _int_ или _ssize\_t_\. Пример:

```cpp
static inline int mesh_x_set(....)
{
 ssize_t len;
 len = read_cb(cb_arg, out, read_len);
 if (len < 0) {
 ....
}
```

**Примечание\.** Кажется с функцией _read\_cb_ вообще всё плохо\. Дело в том, что она объявлено так:

```cpp
static u8_t read_cb(const struct bt_gatt_attr *attr, void *user_data)
```

Тип _u8\_t_ это _unsigned char\._

Функция всегда возвращает только положительные числа типа _unsigned char_\. Если поместить это значение в знаковую переменную типа _int_ или _ssize\_t_, всё равно значение всегда будет положительным\. Следовательно, в других местах проверки на статус ошибки тоже не работают\. Но я не углублялся в изучение этого вопроса\.

**Фрагмент N5, что\-то очень странное**

```cpp
static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}
```

Предупреждение PVS\-Studio: [V575](https://pvs-studio.ru/ru/docs/warnings/v575/) \[CWE\-628\] The 'memcpy' function doesn't copy the whole string\. Use 'strcpy / strcpy\_s' function to preserve terminal null\. shell\.c 427

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

Кто\-то пытался сделать аналог функции _strdup_, но у него это не получилось\.

Начнём с предупреждения анализатора\. Он сообщает, что функция _memcpy_ копирует строчку, но не скопирует терминальный ноль, и это очень подозрительно\.

Кажется, что этот терминальный 0 копируется здесь:

```cpp
((u8_t *)mntpt)[strlen(mntpt)] = '\0';
```

Но нет\! Здесь опечатка, из\-за которой терминальный ноль копируется сам в себя\! Обратите внимание, что запись происходит в массив _mntpt_, а не в _cpy\_mntpt_\. В итоге функция _mntpt\_prepare_ возвращает строку, незавершенную терминальным нулём\.

На самом деле, программист хотел написать так:

```cpp
((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';
```

Однако всё равно не понятно, зачем сделано так сложно\! Этот код можно упростить до следующего варианта:

```cpp
static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}
```

**Фрагмент N6, разыменование указателя до проверки**

```cpp
int bt_mesh_model_publish(struct bt_mesh_model *model)
{
  ....
  struct bt_mesh_model_pub *pub = model->pub;
  ....
  struct bt_mesh_msg_ctx ctx = {
    .send_rel = pub->send_rel,
  };
  ....
  if (!pub) {
    return -ENOTSUP;
  }
  ....
}
```

Предупреждение PVS\-Studio: [V595](https://pvs-studio.ru/ru/docs/warnings/v595/) \[CWE\-476\] The 'pub' pointer was utilized before it was verified against nullptr\. Check lines: 708, 719\. access\.c 708

Очень [распространённый](https://pvs-studio.ru/ru/blog/examples/v595/) паттерн ошибки\. Вначале указатель разыменовывается для инициализации члена структуры:

```cpp
.send_rel = pub->send_rel,
```

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

**Фрагмент N7\-N9, разыменование указателя до проверки**

```cpp
int net_tcp_accept(struct net_context *context, net_tcp_accept_cb_t cb,
                   void *user_data)
{
  ....
  struct tcp *conn = context->tcp;
  ....
  conn->accept_cb = cb;

  if (!conn || conn->state != TCP_LISTEN) {
    return -EINVAL;
  }
  ....
}
```

Предупреждение PVS\-Studio: [V595](https://pvs-studio.ru/ru/docs/warnings/v595/) \[CWE\-476\] The 'conn' pointer was utilized before it was verified against nullptr\. Check lines: 1071, 1073\. tcp2\.c 1071

То же самое, что и в предыдущем случае\. Пояснение здесь не требуется\.

Ещё две такие ошибки можно увидеть здесь:

* V595 \[CWE\-476\] The 'context\-\>tcp' pointer was utilized before it was verified against nullptr\. Check lines: 1512, 1518\. tcp\.c 1512
* V595 \[CWE\-476\] The 'fsm' pointer was utilized before it was verified against nullptr\. Check lines: 365, 382\. fsm\.c 365

**Фрагмент N10, ошибочная проверка**

```cpp
static int x509_get_subject_alt_name( unsigned char **p,
                                      const unsigned char *end,
                                      mbedtls_x509_sequence *subject_alt_name)
{
  ....
    while( *p < end )
    {
        if( ( end - *p ) < 1 )
            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
                    MBEDTLS_ERR_ASN1_OUT_OF_DATA );
    ....
  }
  ....
}
```

Предупреждение PVS\-Studio: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) \[CWE\-570\] Expression '\(end \- \* p\) < 1' is always false\. x509\_crt\.c 635

Внимательно посмотрите на условия:

* \*p < end
* \(end \- \*p\) < 1

Они противоречат друг другу\.

Если \(\*p < end\), то \(end \- \*p\) всегда даст значение 1 или больше\. В общем, здесь что\-то не так, но как должно быть написано правильно, я не знаю\.

**Фрагмент N11, недостижимый код**

```cpp
uint32_t lv_disp_get_inactive_time(const lv_disp_t * disp)
{
    if(!disp) disp = lv_disp_get_default();
    if(!disp) {
        LV_LOG_WARN("lv_disp_get_inactive_time: no display registered");
        return 0;
    }

    if(disp) return lv_tick_elaps(disp->last_activity_time);

    lv_disp_t * d;
    uint32_t t = UINT32_MAX;
    d          = lv_disp_get_next(NULL);
    while(d) {
        t = LV_MATH_MIN(t, lv_tick_elaps(d->last_activity_time));
        d = lv_disp_get_next(d);
    }

    return t;
}
```

Предупреждение PVS\-Studio: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) \[CWE\-571\] Expression 'disp' is always true\. lv\_disp\.c 148

Функция прекращает свою работу, если _disp_ является нулевым указателем\. Далее, наоборот, проверяется, что указатель _disp_ не нулевой \(а это всегда так\), и функция опять\-таки заканчивает свою работу\.

В результате часть кода в функции вообще никогда не получит управление\.

**Фрагмент N12, странное возвращаемое значение**

```cpp
static size_t put_end_tlv(struct lwm2m_output_context *out, u16_t mark_pos,
        u8_t *writer_flags, u8_t writer_flag,
        int tlv_type, int tlv_id)
{
  struct tlv_out_formatter_data *fd;
  struct oma_tlv tlv;
  u32_t len = 0U;

  fd = engine_get_out_user_data(out);
  if (!fd) {
    return 0;
  }

  *writer_flags &= ~writer_flag;

  len = out->out_cpkt->offset - mark_pos;

  /* use stored location */
  fd->mark_pos = mark_pos;

  /* set instance length */
  tlv_setup(&tlv, tlv_type, tlv_id, len);
  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return 0;
}
```

Предупреждение PVS\-Studio: [V1001](https://pvs-studio.ru/ru/docs/warnings/v1001/) The 'len' variable is assigned but is not used by the end of the function\. lwm2m\_rw\_oma\_tlv\.c 338

Функция содержит два оператора _return_, которые оба возвращают 0\. Это странно, что функция всегда возвращает 0\. Ещё странно, что переменная _len_ после присваивания больше никак не используется\. У меня есть большое подозрение, что на самом деле должно быть написано так:

```cpp
  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return len;
}
```

**Фрагмент N13\-N16, ошибка синхронизации**

```cpp
static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    return -ESPIPE;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}
```

Предупреждение PVS\-Studio: [V1020](https://pvs-studio.ru/ru/docs/warnings/v1020/) The function exited without calling the 'k\_mutex\_unlock' function\. Check lines: 620, 549\. nvs\.c 620

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

```cpp
static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    rc = -ESPIPE;
    goto end;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}
```

Ещё три таких ошибки:

* V1020 The function exited without calling the 'k\_mutex\_unlock' function\. Check lines: 574, 549\. nvs\.c 574
* V1020 The function exited without calling the 'k\_mutex\_unlock' function\. Check lines: 908, 890\. net\_context\.c 908
* V1020 The function exited without calling the 'k\_mutex\_unlock' function\. Check lines: 1194, 1189\. shell\.c 1194

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

Надеюсь, вам понравилось\. Приходите к нам в [блог](https://pvs-studio.ru/ru/blog/posts/) читать про проверки других проектов и иные интересные публикации\.

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

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