Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: Стратегия без иллюзий: как превращать цели в результаты - 19.08

>
>
>
Как PVS-Studio улучшает качество...

Как PVS-Studio улучшает качество embedded-проектов

05 Авг 2026

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

Особенности встраиваемых систем

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

Растёт необходимость тщательнее проверять код во встраиваемых системах. На это есть несколько причин:

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

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

Статический анализатор PVS-Studio предлагает ряд механизмов, специально разработанных для embedded-проектов. Они позволяют собрать всю необходимую информацию для анализа из запущенного процесса компиляции.

Специализированные механизмы анализа

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

  • C и C++ компиляторы из GNU Compiler Collection (gcc.exe, g++.exe) и их производные;
  • C и C++ компиляторы Clang (clang.exe) и их производные;
  • Borland C++;
  • QCC;
  • Keil MDK ARM Compiler 5/6;
  • IAR C/C++ Compiler for ARM;
  • Texas Instruments ARM Compiler;
  • GNU Arm Embedded Toolchain;
  • Texas Instruments Code Composer Studio, C6000-CGT, C2000-CGT (будет поддержан в октябре);
  • GNU toolchain for RISC-V.

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

По завершении мониторинга сервер запускает генерацию промежуточных файлов. И затем уже выполняется запуск самого статического анализатора.

Разные механизмы предназначены для разных систем и целей. Разберём каждый из них.

Мониторинг компиляции: CLMonitor.exe (для Windows)

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

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

Запуск мониторинга компиляции

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

CLMonitor.exe monitor

CLMonitor.exe запустится в фоновом режиме для отслеживания всех поддерживаемых компиляторов. Для завершения процесса нужно выполнить одну из команд, которые будут рассмотрены ниже.

Также можно отслеживать только те запуски компиляторов, которые были созданы определённым процессом, указанного через PID. Для этого необходимо запустить CLMonitor.exe в режиме отслеживания с аргументами trace и --parentProcessID (-p).

Строка запуска СLMonitor.exe в таком режиме может выглядеть следующим образом:

CLMonitor.exe trace –-parentProcessID 10256

Если же вы хотите, чтобы CLMonitor.exe отследил только сборку, запускаемую из этой же консоли, то для этого необходимо запустить CLMonitor.exe с аргументом --attach (-a):

CLMonitor.exe monitor –-attach

Сборка проекта

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

Завершение мониторинга компиляции

Выполнить анализ сразу после сборки проекта можно подобной командой:

CLMonitor.exe analyze -l D:\ptest.plog

Также при запуске анализа можно передать дополнительные параметры:

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

CLMonitor.exe abortTrace

Сервер мониторинга CLMonitor.exe позволяет сохранять отловленную информацию о компиляции в отдельном дамп-файле. Благодаря этому в дальнейшем можно запускать анализ без необходимости повторно собирать проект.

Сохранение файла дампа выполняется подобной командой:

CLMonitor.exe saveDump -d D:\monitoring.zip

где -d — путь до итогового файла дампа.

Чтобы запустить анализ, используя сохраненный файл дампа, следует написать следующую команду:

CLMonitor.exe analyzeFromDump -l d:\ptest.plog -d d:\monitoring.zip

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

Режим перехвата Wrap Compilers (для Windows)

Продолжая разговор об особенностях встраиваемых систем, хочется упомянуть об ещё одной сложности анализа embedded-проектов. Зачастую такие проекты состоят из быстро компилирующихся файлов на языке C, и CLMonitor.exe может не успеть определить все файлы исходного кода.

Для того чтобы гарантировать перехват всех процессов компиляции, сервер мониторинга может использовать более агрессивный подход через механизм Image File Execution Options (IFEO) в Windows.

Режим перехвата Wrap Compilers запускает специальный обработчик перед непосредственным запуском каждого процесса компиляции. Он передаёт серверу мониторинга необходимую информацию и продолжает запуск компилятора.

Примечание. Работа этого режима требует доступ к редактированию пути в реестре Windows:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options.

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

Чтобы запустить этот метод отслеживания в консольном варианте мониторинга, необходимо передать серверу мониторинга флаг --wrapCompilers (-W) со списком компиляторов, например:

CLMonitor.exe trace --wrapCompilers gcc.exe,g++.exe

Трассировка компиляции (для Linux)

Анализ проектов на системе Linux осуществляется с использованием файла compile_commands.json. Подробнее о нём можно почитать на соответствующей странице с документацией.

Если сгенерировать файл compile_commands.json невозможно, то можно воспользоваться режимом трассировки компиляции. Для его работы необходимо установить утилиту strace версии 4.7 и выше. С её помощью анализатор собирает необходимую информацию о компиляции проекта во время его сборки.

Собрать проект и отследить процесс его компиляции можно с помощью команды:

pvs-studio-analyzer trace -- build_command -o /path/to/strace_out

где:

  • build_command — команда, используемая для сборки проекта;
  • -o — путь до сохраняемого файла трассировки компиляции. При отсутствии флага файл с именем strace_out сохраняется в CWD.

В результате трассировки по умолчанию будет сформирован файл strace_out.

После получения файла трассировки компиляции strace_out можно запустить анализ следующей командой:

pvs-studio-analyzer analyze -f /path/to/strace_out

Также при запуске анализа можно передать дополнительные параметры:

  • -f — путь до файла с результатами трассировки компиляции;
  • -l — путь до итогового файла отчёта анализатора;
  • -u — путь до файла подавления (suppress-файла);
  • -c — путь до файла конфигурации анализа .pvsconfig;
  • --intermodular — включает режим межмодульного анализа.

Visual Studio Code

Использование мониторинга компиляции также возможно с помощью плагина PVS-Studio для Visual Studio Code.

Для запуска мониторинга компиляции необходимо в палитре команд Visual Studio Code (Ctrl + Shift + P) выбрать команду PVS-Studio: Run compiler monitoring for C and C++.

При её запуске в окне плагина с таблицей появится индикатор того, что мониторинг запущен:

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

Запустить анализ проекта можно, нажав на кнопку или воспользовавшись командой PVS-Studio: Stop monitoring and start analysis в палитре команд Visual Studio Code.

Если мониторинг запускается на проекте впервые, плагин предложит отредактировать файл настроек ./.PVS-Studio/CLMonitorAnalyzerConfig.jsonc.

Нажав Edit, можно установить следующие параметры:

  • путь до файла дампа мониторинга (по умолчанию ./.PVS-Studio/lastMonitoring.zip);
  • путь до файла (директории с файлами) конфигурации анализа .pvsconfig.

Если же нажать Continue, то анализ запустится с заданными по умолчанию настройками.

Если файлы исходного кода, а также конфигурация сборки не менялись, то можно воспользоваться файлом дампа и запустить анализ из него. Для этого необходимо выбрать команду PVS-Studio: Start analysis from compiler monitoring dump file в палитре команд Visual Studio Code.

Также в плагине PVS-Studio для Visual Studio Code мы можем воспользоваться режимом Wrap Compilers. Для этого на вкладке Monitoring (C and C++) в настройках плагина нужно указать имена исполняемых файлов компиляторов, которые будут отслеживаться:

Примечание. Для работы мониторинга в этом режиме необходимо перезапустить Visual Studio Code с правами администратора.

Стандарт для повышения надёжности

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

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

Например, отраслевой стандарт ГОСТ Р МЭК 61508-7 (идентичен IEC 61508-7) предписывает при построении систем с высоким уровнем полноты безопасности (УПБ / SIL) использовать подмножество языка C и C++, стандарты кодирования и статические анализаторы кода. См. таблицу C.1 — Рекомендации по конкретным языкам программирования.

В автомобильной промышленности применяется ГОСТ Р ИСО 26262 (идентичен ISO 26262), который устанавливает требования к функциональной безопасности дорожных транспортных средств. Он также регламентирует процесс верификации модулей программного обеспечения и рекомендует применять статический анализ кода (см. таблицу 7).

Общепринятыми стандартами кодирования, ориентированными на использование безопасного подмножества языков, являются MISRA C и MISRA C++.

Стандарты MISRA — руководство по созданию безопасного программного обеспечения на языках C и C++ для критически важных областей: автомобилестроения, космической отрасли, медицины, промышленной автоматизации и других, где цена ошибки чрезвычайно высока.

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

Авторы тщательно проработали международные стандарты C и C++ и выписали все возможные способы допустить ошибку. Стандарты MISRA C и MISRA C++ содержат список указаний для того, чтобы уменьшить вероятность ошибок, а также улучшить читаемость и сопровождаемость кода.

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

PVS-Studio поддерживает проверку кода в соответствии с различными стандартами MISRA. На текущий момент покрытие выглядит следующим образом (правила категорий Mandatory и Required):

  • MISRA C 2012 — 80%;
  • MISRA C 2023 — 85%;
  • MISRA C++ 2008 — 33%;
  • MISRA C++ 2023 — 35%.

В PVS-Studio версии 7.41 мы завершили работы по покрытию стандарта MISRA C 2023, обеспечив его поддержку на уровне 85%. Мы не останавливаемся на достигнутом. В этом году уже начались работы по расширению поддержки стандарта MISRA C++ 2023.

Подробнее о классификации предупреждений согласно стандартам MISRA C и MISRA C++ можно узнать в соответствующем разделе документации.

При работе со стандартами MISRA будет полезен отчёт MISRA Compliance, который позволяет понять, соответствует ли проект стандарту MISRA C и/или MISRA C++ с учётом всех отклонений и рекатегоризаций.

Подробнее об отчёте MISRA Compliance можно узнать в соответствующем разделе документации.

Опасные места в коде проектов

Мы рассмотрели несколько способов анализа проектов для встраиваемых систем с помощью PVS-Studio. Теперь же предлагаю посмотреть на найденные ошибки в открытых embedded-проектах.

Для рассмотрения возьмем некоторые популярные операционные системы реального времени (RTOS, Real-Time Operating System). Это специализированные ОС, которые гарантируют выполнение задач в строго определённые сроки. Такие системы используются в микроконтроллерах, технике, оборудовании и других встраиваемых системах.

RT-Thread

RT-Thread создан Ричардом Барри в 2003 году, с 2017 года развивается под крылом Amazon Web Services. Широко применяется в микроконтроллерных устройствах благодаря небольшому размеру ядра и высокой переносимости на десятки аппаратных платформ.

Проект был проверен в состоянии на момент коммита cfda3b3.

Фрагмент N1

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

V1031 The 'memcmp' function is not declared. Passing data to or from this function can be affected. dhcp_server_raw.c 151

V1031 The 'strchr' function is not declared. Passing data to or from this function can be affected. dhcp_server_raw.c 718

V647 The value of 'int' type is assigned to the pointer of 'char' type. Consider inspecting the assignment: 'p = strchr(str_tmp, '.')'. dhcp_server_raw.c 718

#include <stdio.h>
#include <stdint.h>
// ....
static struct dhcp_client_node *
dhcp_client_find_by_mac
  (struct dhcp_server *dhcpserver, const u8_t *chaddr, u8_t hlen)
{
  struct dhcp_client_node *node;

  for (node = dhcpserver->node_list; node != NULL; node = node->next)
  {
    if (memcmp(node->chaddr, chaddr, hlen) == 0)               // <=
    {
      return node;
    }
  }

  return NULL;
}
// ....
void dhcpd_start(const char *netif_name)
{
  // ....
  char str_tmp[4 * 4 + 4] = DHCPD_SERVER_IP;
  char *p = str_tmp;
  ip4_addr_t ip_start, ip_end;

  p = strchr(str_tmp, '.');      // <=
  if (p)
  {
    p = strchr(p + 1, '.');      // <=
    if (p)
    {
      p = strchr(p + 1, '.');    // <=
    }
  }
  // ....
}

Два интересных срабатывания, возникших из-за одной ошибки, которая кардинально меняет логику работы всего файла. Анализатор сообщает, что функции memcmp и strchr не задекларированы. При просмотре подключённых заголовочных файлов видно отсутствие <string.h>.

В языке C такой код успешно компилируется, и по умолчанию возвращаемое значение незадекларированной функции — int. Поэтому анализатор выдаёт второе срабатывание: значение типа int присваивается указателю типа char.

Такой код может стать причиной некорректной работы программы, например, как в статье "Красивая 64-битная ошибка на языке Си". Исправить ошибку очень просто — достаточно добавить #include <string.h> в начало файла.

Фрагмент N2

Предупреждение PVS-Studio: V614 Potentially uninitialized pointer 'GPIOx' used. HAL_GPIO.c 48

typedef enum
{
    GPIOA,
    GPIOB,
    GPIOC,
    GPIOD,
}enum_GPIOx_t;

void HAL_GPIO_IRQHandler(enum_GPIOx_t fe_GPIO, uint32_t fu32_GPIO_Pin)
{
  GPIO_TypeDef *GPIOx;              // <=

  switch (fe_GPIO)
  {
    case GPIOA:
    case GPIOB:
    {
      GPIOx = GPIOAB;
    }break;

    case GPIOC:
    case GPIOD:
    {
      GPIOx = GPIOCD;
    }break;

    default: break;                 // <=
  }

  if (fe_GPIO == GPIOB || fe_GPIO == GPIOD )
  {
    fu32_GPIO_Pin <<= 16;
  }

  if (GPIOx->RIS & fu32_GPIO_Pin)   // <=
  {
    GPIOx->IC = fu32_GPIO_Pin;

    /* user can call your application process function here */
    /* ...... */
  }
}

Анализатор предупреждает об использовании неинициализированного указателя. Переменная GPIOx объявляется, но получает значение только в четырёх ветках switch. В default она остаётся неинициализированной, и при попадании в эту ветку дальнейшее использование указателя приведёт к неопределённому поведению.

В языке C вместо enum значения можно передать любое число, в этом случае управление попадет в ветку default, и указатель останется неинициализированным.

Чтобы исправить ошибку, можно добавить обработку в ветке default. Например, выйти из функции при неизвестном значении fe_GPIO:

default:
    return;

Фрагмент N3

Предупреждение PVS-Studio: V570 The 'RTC_DateStruct->RTC_WeekDay' variable is assigned to itself. hk32f0xx_rtc.c 986

void RTC_GetDate(uint32_t RTC_Format, RTC_DateTypeDef *RTC_DateStruct)
{
  uint32_t tmpreg = 0;
  // ....
  /* Check the input parameters format */
  if (RTC_Format == RTC_Format_BIN)
  {
    /* Convert the structure parameters to Binary format */
    RTC_DateStruct->RTC_Year = 
                        (uint8_t)RTC_Bcd2ToByte(RTC_DateStruct->RTC_Year);
    RTC_DateStruct->RTC_Month = 
                        (uint8_t)RTC_Bcd2ToByte(RTC_DateStruct->RTC_Month);
    RTC_DateStruct->RTC_Date = 
                        (uint8_t)RTC_Bcd2ToByte(RTC_DateStruct->RTC_Date);
    RTC_DateStruct->RTC_WeekDay = 
                        (uint8_t)(RTC_DateStruct->RTC_WeekDay);  // <=
  }
}

Это довольно интересная ошибка. Этот фрагмент кода пришлось отформатировать, потому что в длину он был довольно большим, поэтому здесь сразу видно проблему. Разработчик, скорее всего, копировал строки, преобразовывая в binary формат, но в последней строке (золотая классика) забыл вызвать функцию RTC_Bcd2ToByte. В итоге переменная RTC_WeekDay присваивается самой себе, что не имеет смысла.

Исправленный код:

RTC_DateStruct->RTC_WeekDay = 
                    (uint8_t) RTC_Bcd2ToByte(RTC_DateStruct->RTC_WeekDay);

Фрагмент N4

Предупреждение PVS-Studio: V595 The 'cond' pointer was utilized before it was verified against nullptr. Check lines: 346, 353. pthread_cond.c 346

rt_err_t _pthread_cond_timedwait(pthread_cond_t *cond,
                                 pthread_mutex_t *mutex,
                                 rt_int32_t timeout)
{
  rt_err_t result = RT_EOK;
  rt_sem_t sem;
  rt_int32_t time;

  sem = &(cond->sem);    // <=
  if (sem == RT_NULL)
  {
      return -RT_ERROR;
  }
  time = timeout;

  if (!cond || !mutex)   // <=
  {
    return -RT_ERROR;
  }
  // ....
}

Разработчик использовал указатель cond до проверки на NULL. Такие ошибки встречаются в проектах очень часто.

Здесь могут быть два варианта:

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

Чтобы исправить ошибку, нужно добавить проверку в начале функции. Например, так:

rt_err_t _pthread_cond_timedwait(pthread_cond_t *cond,
                                 pthread_mutex_t *mutex,
                                 rt_int32_t timeout)
{
  rt_err_t result = RT_EOK;
  rt_sem_t sem;
  rt_int32_t time;

  if (!cond || !mutex)
  {
    return -RT_ERROR;
  }

  sem = &(cond->sem);
  if (sem == RT_NULL)
  {
      return -RT_ERROR;
  }
  time = timeout;

  // ....
}

FreeRTOS

FreeRTOS появился в Китае в 2006 году и развивается командой RT-Thread Development Team. Активно используется в IoT-устройствах, бытовой технике и промышленном оборудовании. Предлагает развитую экосистему со множеством встроенных компонентов, приближаясь к полноценной операционной системе.

Проект был проверен в состоянии на момент коммита c73a397.

Фрагмент N1

Предупреждение PVS-Studio: V557 Array overrun is possible. The value of 'uxTimerID' index could reach 21. TimerDemo.c 1167

static uint8_t ucAutoReloadTimerCounters[configTIMER_QUEUE_LENGTH + 1] = { 0 };
// ....

static void prvAutoReloadTimerCallback( TimerHandle_t pxExpiredTimer )
{
  size_t uxTimerID;

  uxTimerID = ( size_t ) pvTimerGetTimerID( pxExpiredTimer );

  if( uxTimerID <= ( configTIMER_QUEUE_LENGTH + 1 ) )     // <=
  {
    ( ucAutoReloadTimerCounters[ uxTimerID ] )++;
  // ....
}

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

Проблема в том, что разработчик использовал нестрогое сравнение <= для задания границ индекса uxTimerID. В результате переменная может иметь значение configTIMER_QUEUE_LENGTH + 1, которым как раз и задан размер массива. При доступе к элементу с таким индексом произойдёт выход за границы массива.

Исправленный вариант кода:

if( uxTimerID < ( configTIMER_QUEUE_LENGTH + 1 ) )

Фрагмент N2

Предупреждение PVS-Studio: V547 Expression 'i + 1 > (4 + 32)' is always false. trcSnapshotRecorder.c 831

static uint8_t writeInt8(void * buffer, uint8_t i, uint8_t value)
{
  TRACE_ASSERT(buffer != (void*)0, "writeInt8: buffer == NULL", 0);

  if (i >= MAX_ARG_SIZE)
  {
    return 255;
  }

  ((uint8_t*)buffer)[i] = value;

  if (i + 1 > MAX_ARG_SIZE)
  {
    return 255;
  }

  return ((uint8_t) (i + 1));
}

Ошибка заключается в том, что после первой проверки if (i >= MAX_ARG_SIZE) вторая проверка if (i + 1 > MAX_ARG_SIZE) становится всегда ложной.

Если i прошёл первую проверку, то i + 1 уже не может превышать MAX_ARG_SIZE. Скорее всего, одна из проверок избыточна или должна была быть другой.

Zephyr

Zephyr стартовал в 2016 году при поддержке Intel и сейчас развивается под управлением Linux Foundation. Применяется в IoT, микроэлектронике и автомобильных системах. Содержит модульную архитектуру с большим количеством встроенных подсистем.

Проект был проверен в состоянии на момент коммита c6da464.

Фрагмент N1

Предупреждение PVS-Studio: V547 Expression 'conv->pad0_value > 0' is always true. cbprintf_complete.c 1224

static char *encode_float(/*....*/)
{
  // ....
  if ((decexp < 0) && (precision > 0)) {
    conv->pad0_value = -decexp;
    if (conv->pad0_value > precision) {
      conv->pad0_value = precision;
    }

    precision -= conv->pad0_value;
    conv->pad_postdp = (conv->pad0_value > 0); // <=
  }
  // ....
}

Анализатор предупреждает, что условие conv->pad0_value > 0 всегда истинно, а значит в переменную conv->pad_postdp записывается одно и то же значение. Почему это условие всегда истинно:

  • Как только мы заходим в первый if, conv->pad0_value получает значение -decexp. Поскольку decexp < 0, результат всегда положительный.
  • Если мы зайдём во вложенный if, значение переменной остаётся положительным, так как precision > 0.
  • В итоге conv->pad0_value в любом случае будет больше нуля.

Скорее всего, проверку можно убрать либо логику инициализации pad0_value стоит вынести за пределы блока if. Окончательное решение остаётся за разработчиками.

Фрагмент N2

Предупреждение PVS-Studio: V557 Array overrun is possible. The value of 'keep_cnt ++' index could reach 16. cbprintf_packaged.c 1143

int cbprintf_package_convert(/*....*/)
{
  // ....
  __ASSERT_NO_MSG(keep_cnt < sizeof(keep_str_pos));
  if (keep_cnt < sizeof(keep_str_pos)) {
    keep_str_pos[keep_cnt++] = arg_idx;
    keep_str_pos[keep_cnt++] = arg_pos;
  }
  // ....
}

Довольно интересный случай. Разработчик корректно проверил границу массива, но не учёл поведение пост-инкремента. Как это работает:

  • условие keep_cnt < sizeof(keep_str_pos) позволяет keep_cnt быть равным N - 1, где N — размер массива;
  • при первом пост-инкременте keep_cnt увеличивается до N, доступ к массиву осуществляется по индексу N - 1;
  • при втором пост-инкременте keep_cnt увеличивается до N + 1, доступ к массиву осуществляется по индексу N.

Выход за границы массива — это неопределённое поведение. Исправить код можно, задав условие по-другому:

if (keep_cnt + 1 < sizeof(keep_str_pos))

Фрагмент N3

Предупреждение PVS-Studio: V779 Unreachable code detected. It is possible that an error is present. sched.c 345

#define z_except_reason(reason) do { \
    __EXCEPT_LOC();              \
    z_fatal_error(reason, NULL); \
  } while (false)

#define k_panic()  z_except_reason(K_ERR_KERNEL_PANIC)

void z_thread_halt(/*....*/)
{
  // ....
  if ((thread == _current) && !arch_is_in_isr()) {
    if (z_is_thread_essential(thread)) {
      k_spin_unlock(&_sched_spinlock, key);
      k_panic();                                   // <=
      key = k_spin_lock(&_sched_spinlock);         // <=
    }
    // ....
}

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

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

Фрагмент N4

Предупреждение PVS-Studio: V795 Please note that the size of the 'time_t' type is not 64 bits. After year 2038, the program will work incorrectly. clock.c 47

static void timespec_from_ticks(uint64_t ticks, struct timespec *ts)
{
  uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
  uint64_t nremainder = ticks % CONFIG_SYS_CLOCK_TICKS_PER_SEC;

  *ts = (struct timespec){
    .tv_sec = (time_t)elapsed_secs,
    /* For ns 32 bit conversion can be used since its smaller than 1sec. */
    .tv_nsec = (int32_t)k_ticks_to_ns_floor32(nremainder),
  };
}

Проблемы будущего уже стучатся в дверь. Через 12 лет, 19 января 2038 года, в проекте возникнет классическая проблема 2038 года.

Причина в том, что тип time_t перестанет работать как раньше. Поведение будет определяться платформой. Это связанно с тем, что тип представляет собой количество секунд, прошедшее с 1 января 1970 года. После 2038 года значение переполнится, и работа со временем станет некорректной.

Способ решения проблемы

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

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

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

Дополнительные ссылки

Подписаться на рассылку
Хотите раз в месяц получать от нас подборку вышедших в этот период самых интересных статей и новостей? Подписывайтесь!
Популярные статьи по теме

Комментарии (0)

Следующие комментарии next comments
close comment form