Статический анализатор кода PVS-Studio 6.22 адаптирован для ARM-компиляторов (Keil, IAR)

Андрей Карпов
Статей: 396

PVS-Studio - это статический анализатор кода для поиска ошибок и потенциальных уязвимостей в коде программ на языке C, C++ и C#. Мы давно радуем читателей нашего блога проверкой открытых проектов и разбором найденных ошибок. Наши статьи имеют потенциал стать более интересными, так как анализатор научился проверять код встроенных устройств. Мы поддержали несколько ARM-компиляторов, про которые подробнее вы узнаете из статьи. Ошибки во встроенных устройствах и роботах могут быть более зрелищными, чем в прикладных программах. Ошибка во встроенном устройстве - это не просто падение/зависание программы или неправильная картинка. Это сошедший с ума Wi-Fi-чайник, который будет кипятить воду, пока она не выкипит и не сработает термопредохранитель. В общем, с ошибками в мире embedded-систем всё обстоит куда интереснее и страшнее.

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image1.png

Мой самый зрелищный баг

За свою карьеру программиста я сделал немало ошибок в коде. Однако ошибки в каком-то смысле были скучными. Что-то не так работало, где-то разыменовывался нулевой указатель и так далее. Да, это были настоящие ошибки, которые требовали исправления. Однако самое яркое впечатление от собственной ошибки я получил, развлекаясь с самодельными роботами.

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

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image2.png

Бот реализован на базе микроконтроллера ATmega8A (8 Кбайт Flash, 512 байт EEPROM, 1 Кбайт RAM). В первом варианте программы один из таймеров микроконтроллера генерировал прерывание, в обработчике которого читались команды с пульта дистанционного управления. Если были какие-то команды, то они записывались в FIFO-буфер, из которого затем извлекались и исполнялись в главном цикле программы. Команды были такие, как: едем вперёд/назад; разворачиваемся влево/вправо; едем вперёд, поворачивая немного влево; хватаем мышку; пинаем мячик и так далее.

На самом деле, я всё переусложнил. Позже я избавился от FIFO-буфера и вообще написал всё более просто и красиво.

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

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image4.png

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

Это было самое большое впечатление от ошибки в программе, полученное мною за все мои годы программирования. Одно дело, когда программа падает из-за переполнения стека, и совсем другое, когда перед тобой носится чокнутый робот, которого ты сам сделал и даже не понимаешь, как такое может быть. Жаль, что я не догадался заснять это действо и свои эмоции на заднем плане :).

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

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

PVS-Studio 6.22

В новой версии анализатора PVS-Studio 6.22 наша команда доработала инфраструктуру для проверки проектов следующего типа:

  • Появилась поддержка ARM Compiler 5 и ARM Compiler 6 в составе среды Keil uVision 5.
  • Компиляторов ARM Compiler 5 и ARM Compiler 6 в составе среды Keil DS-MDK.
  • Мы поддерживаем IAR C/C++ Compiler for ARM в составе среды IAR Embedded Workbench.

Проект RT-Thread

Для демонстрации новых возможностей PVS-Studio мне понадобился открытый проект, и выбор пал на RT-Thread. Этот проект можно собрать в режиме gcc/keil/iar. С целью дополнительного тестирования анализатора мы проверили его как в режиме Keil, так и в режиме IAR. Отчёты получились практически одинаковыми, так что я даже не помню, с каким из них я в дальнейшем работал.

Давайте теперь немного поговорим о самом проекте RT-Thread.

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image6.png

RT-Thread is an open source IoT operating system from China, which has strong scalability: from a tiny kernel running on a tiny core, for example ARM Cortex-M0, or Cortex-M3/4/7, to a rich feature system running on MIPS32, ARM Cortex-A8, ARM Cortex-A9 DualCore etc.

Официальный сайт: rt-thread.org.

Исходный код: rt-thread.

Думаю, операционная система RT-Thread - очень хороший кандидат, чтобы стать первым embedded-проектом, проверенным с помощью PVS-Studio.

Ошибки, замеченные в проекте RT-Thread

Я бегло просмотрел отчёт анализатора PVS-Studio и отобрал 95 предупреждений, представляющих, на мой взгляд, наибольший интерес. Вы можете ознакомиться с ними, скачав архив rt-thread-html-log.zip с полным HTML-отчётом. Этот формат мы реализовали недавно, и не все пользователи знают про него. Поэтому я решил воспользоваться подходящей возможностью, чтобы вновь написать о нём. Вот как выглядит этот отчёт, открытый в Firefox (нажмите на изображение для увеличения):

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image7.png

Отчёт сделан по аналогии с HTML отчётом, который генерирует анализатор Clang. Отчёт хранит в себе часть исходного кода, и вы можете сразу увидеть, к какому фрагменту программы относятся предупреждения. Просмотр одного из предупреждений выглядит следующим образом (нажмите на картинку для увеличения):

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image9.png

Нет смысла рассматривать в статье все 95 предупреждений, так как многие из них похожи. В статье приведу только 14 фрагментов кода, которые мне показались по какой-то причине заслуживающими описания.

Примечание. Я мог пропустить важные предупреждения, указывающие на серьёзные ошибки. Поэтому прошу разработчиков RT-Thread не полагаться только на мой отчёт, содержащий 95 предупреждений, а провести анализ проекта самостоятельно. Вдобавок мне кажется, мы не разобрались как следует с проектом RT-Thread и проверили только какую-то его часть.

Фрагмент N1. CWE-562: Return of Stack Variable Address

void SEMC_GetDefaultConfig(semc_config_t *config)
{
  assert(config);

  semc_axi_queueweight_t queueWeight; /*!< AXI queue weight. */
  semc_queuea_weight_t queueaWeight;
  semc_queueb_weight_t queuebWeight;

  ....

  config->queueWeight.queueaWeight = &queueaWeight;
  config->queueWeight.queuebWeight = &queuebWeight;
}

Предупреждение PVS-Studio: V506 CWE-562 Pointer to local variable 'queuebWeight' is stored outside the scope of this variable. Such a pointer will become invalid. fsl_semc.c 257

Функция записывает во внешнюю структуру адреса двух локальных переменных (queueaWeight и queuebWeight). После выхода из функции переменные перестают существовать, но структура по-прежнему будет хранить и использовать указатели на эти уже несуществующие объекты. Фактически, указатели указывают в какое-то место стека, в котором может быть всё что угодно. Это очень неприятная ошибка безопасности.

Анализатор PVS-Studio сообщает только о последнем подозрительном присваивании, что связано с некоторыми внутренними особенностями его работы. Однако, если последнее присваивание удалить или исправить, то анализатор начнёт предупреждать о первом присваивании.

Фрагмент N2. CWE-570: Expression is Always False

#define CAN_FIFO0   ((uint8_t)0x00U)   /*!< receive FIFO0 */
#define CAN_FIFO1   ((uint8_t)0x01U)   /*!< receive FIFO1 */

uint8_t can_receive_message_length(uint32_t can_periph,
                                   uint8_t fifo_number)
{
  uint8_t val = 0U;
    
  if(CAN_FIFO0 == fifo_number){
    val = (uint8_t)(CAN_RFIFO0(can_periph) & CAN_RFIFO_RFL0_MASK);
  }else if(CAN_FIFO0 == fifo_number){
    val = (uint8_t)(CAN_RFIFO1(can_periph) & CAN_RFIFO_RFL0_MASK);
  }else{
    /* illegal parameter */
  }
  return val;
}

Предупреждение PVS-Studio: V517 CWE-570 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 525, 527. gd32f4xx_can.c 525

Если аргумент fifo_number не равен CAN_FIFO0, то функция всегда возвращает 0. Код, скорее всего, писался с помощью Copy-Paste и по невнимательности в скопированном фрагменте забыли заменить константу CAN_FIFO0 на CAN_FIFO1.

Фрагмент N3. CWE-571: Expression is Always True

#define PECI_M0D0C_HITHR_M      0xFFFF0000  // High Threshold
#define PECI_M0D0C_LOTHR_M      0x0000FFFF  // Low Threshold
#define PECI_M0D0C_HITHR_S      16
#define PECI_M0D0C_LOTHR_S      0

void
PECIDomainConfigGet(....)
{
  unsigned long ulTemp;
  ....
  ulTemp = HWREG(ulBase + PECI_O_M0D0C + (ulDomain * 4));
  *pulHigh =
    ((ulTemp && PECI_M0D0C_HITHR_M) >> PECI_M0D0C_HITHR_S);
  *pulLow =
    ((ulTemp && PECI_M0D0C_LOTHR_M) >> PECI_M0D0C_LOTHR_S);
}

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

  • V560 CWE-571 A part of conditional expression is always true: 0xFFFF0000. peci.c 372
  • V560 CWE-571 A part of conditional expression is always true: 0x0000FFFF. peci.c 373

Две досадные опечатки: вместо двух операторов & дважды использовали оператор &&.

Из-за этого переменной pulHigh всегда будет присваиваться значение 0, а переменной pulLow будет присвоено значение 0 или 1. Это явно не то, что задумывал программист.

Пояснение для тех, кто слабо знаком с языком C. Результатом выражения (ulTemp && PECI_M0D0C_xxxxx_M) всегда является 0 или 1. Далее 0 или 1 сдвигаются вправо. При сдвиге вправо значения 0/1 на 16 бит всегда будет получаться 0. При сдвиге значения 0/1 на 0 бит по-прежнему получается 0 или 1.

Фрагмент N4. CWE-480: Use of Incorrect Operator

typedef enum _aipstz_peripheral_access_control {
  kAIPSTZ_PeripheralAllowUntrustedMaster = 1U,
  kAIPSTZ_PeripheralWriteProtected = (1U < 1),
  kAIPSTZ_PeripheralRequireSupervisor = (1U < 2),
  kAIPSTZ_PeripheralAllowBufferedWrite = (1U < 2)
} aipstz_peripheral_access_control_t;
https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image11.png

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

  • V602 CWE-480 Consider inspecting the '(1U < 1)' expression. '<' possibly should be replaced with '<<'. fsl_aipstz.h 69
  • V602 CWE-480 Consider inspecting the '(1U < 2)' expression. '<' possibly should be replaced with '<<'. fsl_aipstz.h 70
  • V602 CWE-480 Consider inspecting the '(1U < 2)' expression. '<' possibly should be replaced with '<<'. fsl_aipstz.h 71

Именованные константы должны были являться степенями двойки и быть равны следующим значениям: 1, 2, 4, 4. Но вместо оператора << программист случайно написал <. В результате получаются следующие значения:

  • kAIPSTZ_PeripheralAllowUntrustedMaster = 1
  • kAIPSTZ_PeripheralWriteProtected = 0
  • kAIPSTZ_PeripheralRequireSupervisor = 1
  • kAIPSTZ_PeripheralAllowBufferedWrite = 1

Фрагмент N5. CWE-834: Excessive Iteration

static int ft5x06_dump(void)
{
  uint8_t i;
  uint8_t reg_value;
    
  DEBUG_PRINTF("[FTS] Touch Chip\r\n");
        
  for (i = 0; i <= 255; i++)
  {
    _ft5x06_read(i, ®_value, 1);
    
    if (i % 8 == 7)
      DEBUG_PRINTF("0x%02X = 0x%02X\r\n", i, reg_value);
    else
      DEBUG_PRINTF("0x%02X = 0x%02X ", i, reg_value);
  }
  DEBUG_PRINTF("\n");
   
  return 0;
}

Предупреждение PVS-Studio: V654 CWE-834 The condition 'i <= 255' of loop is always true. drv_ft5x06.c 160

Переменные типа uint8_t могут хранить значения в диапазоне [0..255], поэтому условие i <= 255 всегда истинно. Из-за этого цикл будет бесконечно распечатывать отладочные данные.

Фрагмент N6. CWE-571: Expression is Always True

#define RT_CAN_MODE_NORMAL              0
#define RT_CAN_MODE_LISEN               1
#define RT_CAN_MODE_LOOPBACK            2
#define RT_CAN_MODE_LOOPBACKANLISEN     3

static rt_err_t control(struct rt_can_device *can,
                        int cmd, void *arg)
{
  ....
  case RT_CAN_CMD_SET_MODE:
    argval = (rt_uint32_t) arg;
    if (argval != RT_CAN_MODE_NORMAL ||
        argval != RT_CAN_MODE_LISEN ||
        argval != RT_CAN_MODE_LOOPBACK ||
        argval != RT_CAN_MODE_LOOPBACKANLISEN)
    {
      return RT_ERROR;
    }
    if (argval != can->config.mode)
    {
      can->config.mode = argval;
      return bxcan_set_mode(pbxcan->reg, argval);
    }
    break;
  ....
}

Предупреждение PVS-Studio: V547 CWE-571 Expression is always true. Probably the '&&' operator should be used here. bxcan.c 1171

Случай RT_CAN_CMD_SET_MODE всегда обрабатывается неверно. Дело в том, что условие вида (x !=0 || x != 1 || x != 2 || x != 3) всегда является истинным. Скорее всего, мы имеем дело с очередной опечаткой и, на самом деле, здесь должно быть написано:

if (argval != RT_CAN_MODE_NORMAL &&
    argval != RT_CAN_MODE_LISEN &&
    argval != RT_CAN_MODE_LOOPBACK &&
    argval != RT_CAN_MODE_LOOPBACKANLISEN)

Фрагмент N7. CWE-687: Function Call With Incorrectly Specified Argument Value

void MCAN_SetSTDFilterElement(CAN_Type *base,
  const mcan_frame_filter_config_t *config,
  const mcan_std_filter_element_config_t *filter,
  uint8_t idx)
{
  uint8_t *elementAddress = 0;
  elementAddress = (uint8_t *)(MCAN_GetMsgRAMBase(base) +
                               config->address + idx * 4U);
  memcpy(elementAddress, filter, sizeof(filter));
}

Анализатор указывает на ошибку сразу двумя разными предупреждениями:

  • V579 CWE-687 The memcpy function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. fsl_mcan.c 418
  • V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'filter' class object. fsl_mcan.c 418

Функция memcpy копирует не всю структуру типа mcan_std_filter_element_config_t, а только её часть, равную размеру одного указателя.

Фрагмент N8. CWE-476: NULL Pointer Dereference

Не обошлось в коде RT-Thread без ошибок, когда указатель разыменовывается до его проверки. Это очень распространённый тип ошибки.

static rt_size_t rt_sdcard_read(rt_device_t dev,
                                rt_off_t    pos,
                                void       *buffer,
                                rt_size_t   size)
{
  int i, addr;
  struct dfs_partition *part =
    (struct dfs_partition *)dev->user_data;

  if (dev == RT_NULL)
  {
    rt_set_errno(-EINVAL);
    return 0;
  }
  ....
}

Предупреждение PVS-Studio: V595 CWE-476 The 'dev' pointer was utilized before it was verified against nullptr. Check lines: 497, 499. sdcard.c 497

Фрагмент N9. CWE-563: Assignment to Variable without Use

static void enet_default_init(void)
{
  ....
  reg_value = ENET_DMA_BCTL;
  reg_value &= DMA_BCTL_MASK;
  reg_value = ENET_ADDRESS_ALIGN_ENABLE 
             |ENET_ARBITRATION_RXTX_2_1
             |ENET_RXDP_32BEAT |ENET_PGBL_32BEAT 
             |ENET_RXTX_DIFFERENT_PGBL
             |ENET_FIXED_BURST_ENABLE |ENET_MIXED_BURST_DISABLE
             |ENET_NORMAL_DESCRIPTOR;
  ENET_DMA_BCTL = reg_value; 
  ....
}

Предупреждение PVS-Studio: V519 CWE-563 The 'reg_value' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 3427, 3428. gd32f4xx_enet.c 3428

Присваивание reg_value = ENET_ADDRESS_ALIGN_ENABLE|.... перетирает предыдущее значение переменной reg_value. Это странно, так как в переменной хранится результат осмысленных вычисления. Скорее всего, код должен быть таким:

reg_value = ENET_DMA_BCTL;
reg_value &= DMA_BCTL_MASK;
reg_value |= ENET_ADDRESS_ALIGN_ENABLE 
           |ENET_ARBITRATION_RXTX_2_1
           |ENET_RXDP_32BEAT |ENET_PGBL_32BEAT 
           |ENET_RXTX_DIFFERENT_PGBL
           |ENET_FIXED_BURST_ENABLE |ENET_MIXED_BURST_DISABLE
           |ENET_NORMAL_DESCRIPTOR;

Фрагмент N10. CWE-665: Improper Initialization

typedef union _dcp_hash_block
{
    uint32_t w[DCP_HASH_BLOCK_SIZE / 4];
    uint8_t b[DCP_HASH_BLOCK_SIZE];
} dcp_hash_block_t;

typedef struct _dcp_hash_ctx_internal
{
  dcp_hash_block_t blk;
  ....
} dcp_hash_ctx_internal_t;

status_t DCP_HASH_Init(DCP_Type *base, dcp_handle_t *handle,
                       dcp_hash_ctx_t *ctx, dcp_hash_algo_t algo)
{
  ....
  dcp_hash_ctx_internal_t *ctxInternal;
  ....
  for (i = 0; i < sizeof(ctxInternal->blk.w) /
                            sizeof(ctxInternal->blk.w[0]); i++)
  {
     ctxInternal->blk.w[0] = 0u;
  }
  ....
}

Предупреждение PVS-Studio: V767 Suspicious access to element of 'w' array by a constant index inside a loop. fsl_dcp.c 946

Анализатор не смог сопоставить с этим предупреждением никакой CWE ID, однако по смыслу это должно быть CWE-665: Improper Initialization.

В цикле значение 0 записывается всё время в нулевой элемент массива, в то время как остальные элементы остаются неинициализированными.

Фрагмент N11. CWE-571: Expression is Always True

static void at91_mci_init_dma_read(struct at91_mci *mci)
{
  rt_uint8_t i;
  ....
  for (i = 0; i < 1; i++) 
  {
    /* Check to see if this needs filling */
    if (i == 0) 
    {
      if (at91_mci_read(AT91_PDC_RCR) != 0) 
      {
        mci_dbg("Transfer active in current\n");
        continue;
      }
    }
    else {
      if (at91_mci_read(AT91_PDC_RNCR) != 0)
      {
        mci_dbg("Transfer active in next\n");
        continue;
      }
    }

    length = data->blksize * data->blks;
    mci_dbg("dma address = %08X, length = %d\n",
            data->buf, length);

    if (i == 0) 
    {
      at91_mci_write(AT91_PDC_RPR, (rt_uint32_t)(data->buf));
      at91_mci_write(AT91_PDC_RCR, .....);
    }
    else 
    {
      at91_mci_write(AT91_PDC_RNPR, (rt_uint32_t)(data->buf));
      at91_mci_write(AT91_PDC_RNCR, .....);
    }
  }
  ....
}

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

  • V547 CWE-571 Expression 'i == 0' is always true. at91_mci.c 196
  • V547 CWE-571 Expression 'i == 0' is always true. at91_mci.c 215

Тело цикла выполняется ровно один раз. Это не имеет смысла. Зачем тогда вообще писать цикл?

Более того, так как в теле цикла переменная i всегда равна 0, то некоторые условия всегда истинные и часть кода никогда не выполняется.

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

for (i = 0; i <= 1; i++)

В этом случае код функции обретёт смысл.

Фрагмент N12. CWE-457: Use of Uninitialized Variable

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

void LCD_PutPixel (LCD_PANEL panel, uint32_t X_Left,
                   uint32_t Y_Up, LcdPixel_t color)
{
  uint32_t k;
  uint32_t * pWordData = NULL;
  uint8_t*   pByteData = NULL;
  uint32_t  bitOffset;
  uint8_t*   pByteSrc = (uint8_t*)&color;
  uint8_t  bpp = bits_per_pixel[lcd_config.lcd_bpp];
  uint8_t  bytes_per_pixel = bpp/8;
  uint32_t start_bit;
  
  if((X_Left >= lcd_hsize)||(Y_Up >= lcd_vsize))
    return;

  if(panel == LCD_PANEL_UPPER)
    pWordData = (uint32_t*) LPC_LCD->UPBASE +
                            LCD_GetWordOffset(X_Left,Y_Up);
  else
    pWordData = (uint32_t*) LPC_LCD->LPBASE +
                            LCD_GetWordOffset(X_Left,Y_Up);
    
  bitOffset = LCD_GetBitOffset(X_Left,Y_Up);
  pByteData = (uint8_t*) pWordData;
  pByteData += bitOffset/8;
    
  start_bit =  bitOffset%8;

  if(bpp < 8)
  {
    uint8_t bit_pos = start_bit;
    uint8_t bit_ofs = 0;
    for(bit_ofs = 0;bit_ofs <bpp; bit_ofs++,bit_pos++)
    {
      *pByteData &= ~ (0x01 << bit_pos);
      *pByteData |=
        ((*pByteSrc >> (k+bit_ofs)) & 0x01) << bit_pos;   // <=
    }
  }
  ....
}

Предупреждение PVS-Studio: V614 CWE-457 Uninitialized variable 'k' used. lpc_lcd.c 510

Переменная k нигде не инициализируется до момента её использования в выражении:

*pByteData |= ((*pByteSrc >> (k+bit_ofs)) & 0x01) << bit_pos;

Фрагмент N13. CWE-670: Always-Incorrect Control Flow Implementation

HAL_StatusTypeDef FMC_SDRAM_SendCommand(....)
{
  ....

  /* wait until command is send */
  while(HAL_IS_BIT_SET(Device->SDSR, FMC_SDSR_BUSY))
  {
    /* Check for the Timeout */
    if(Timeout != HAL_MAX_DELAY)
    {
      if((Timeout == 0)||((HAL_GetTick() - tickstart) > Timeout))
      {
        return HAL_TIMEOUT;
      }
    }     
    
    return HAL_ERROR;
  }
  
  return HAL_OK;  
}

Предупреждение PVS-Studio: V612 CWE-670 An unconditional 'return' within a loop. stm32f7xx_ll_fmc.c 1029

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

Фрагмент N14. Прочее

Как я уже говорил ранее, я привёл в статье только некоторые ошибки. Полный список выбранных мною предупреждений можно найти в HTML-отчёте (архив с отчётом: rt-thread-html-log.zip).

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

typedef unsigned long  rt_uint32_t;
static rt_err_t lpc17xx_emac_init(rt_device_t dev)
{
  ....
  rt_uint32_t regv, tout, id1, id2;
  ....
  LPC_EMAC->MCFG = MCFG_CLK_DIV20 | MCFG_RES_MII;
  for (tout = 100; tout; tout--);
  LPC_EMAC->MCFG = MCFG_CLK_DIV20;
  ....
}

Предупреждение PVS-Studio: V529 CWE-670 Odd semicolon ';' after 'for' operator. emac.c 182

С помощью цикла программист осуществил маленькую задержку. Анализатор, пусть и косвенно, обращает на это наше внимание.

В моём мире оптимизирующих компиляторов это явная ошибка. Компиляторы просто выкинут этот цикл и никакой задержки не будет, т.к. tout - это обыкновенная не volatile переменная. Как дело обстоит в embedded-мире я не знаю, но подозреваю, что код всё равно некорректен или, по крайней мере, ненадёжен. Даже если компилятор не выкидывает такие циклы, непонятно, сколько по времени будет длиться задержка и будет ли она достаточна.

Насколько я знаю, в подобных системах есть функции типа sleep_us. Их и следует использовать для маленьких задержек. Компилятор вполне может превратить вызов функции sleep_us в обыкновенный простой цикл, но это уже особенности реализации. Руками же писать такие циклы задержки некрасиво и опасно.

Заключение

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

Вы можете скачать демонстрационную версию PVS-Studio здесь.

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

https://import.viva64.com/docx/blog/0561_KEIL_IAR_ru/image12.png

Спасибо всем за внимание и безбажных вам роботов!

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

Эта статья привлечёт новую аудиторию. Поэтому тем, кто ещё не был знаком с инструментом PVS-Studio, предлагаю ознакомиться со следующими статьями:


Вы можете обсудить эту статью с другими читателями на сайте habr.com


Найдите ошибки в своем C, C++, C# и Java коде

Предлагаем попробовать проверить код вашего проекта с помощью анализатора кода PVS-Studio. Одна найденная в нём ошибка скажет вам о пользе методологии статического анализа кода больше, чем десяток статей.

goto PVS-Studio;

Андрей Карпов
Статей: 396


Найденные ошибки

Проверено проектов
409
Собрано ошибок
14 072

А ты совершаешь ошибки в коде?

Проверь с помощью
PVS-Studio

Статический анализ
кода для C, C++, C#
и Java

goto PVS-Studio;
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять