Вчера ради интереса проверил проект Small Device C Compiler с помощью PVS-Studio: https://sourceforge.net/projects/sdcc/

SDCC — это переназначаемый, оптимизирующий компилятор стандарта C (ANSI C89/ISO C90, ISO C99, ISO C11/C17), предназначенный для растущего списка процессоров, включая Intel 8051, Maxim 80DS390, Zilog Z80, Z180, eZ80 (в режиме Z80) , Rabbit 2000, GameBoy, Motorola 68HC08, S08, STMicroelectronics STM8 и Padauk PDK14 и PDK15.

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

Фрагмент N1

class cl_timer2: public cl_timer0
{
protected:
  class cl_address_space *sfr;
  ....
}
void
cl_timer2::print_info(class cl_console_base *con)
{
  ....
  con->dd_printf(" %s", sfr?"?":((sfr->get(IE)&bmET2)?"en":"dis"));
  ....
}

Предупреждение PVS-Studio: V522 [CWE-476] Может иметь место разыменование нулевого указателя sfr. timer2.cc 403

Таким образом, член класса sfr потенциально может быть равен nullptr. Об этом говорит проверка, реализованная тернарным оператором. Однако, если указатель нулевой, то он будет немедленно разыменован. Скорее всего, указатель почти всегда ненулевой, но вместо какой-то полезной информации печатается вопросительный знак.

Я предполагаю, что на самом деле условие должно было быть обратным:

(sfr == nullptr)?"?":((sfr->get(IE)&bmET2)?"en":"dis")

И как это часто бывает, ошибка копировалась с помощью Copy-Paste :)

  • V522 [CWE-476] Может иметь место разыменование нулевого указателя «sfr». timer1.cc 86
  • V522 [CWE-476] Может иметь место разыменование нулевого указателя «sfr». timer0.cc 426

Фрагмент N2

t_mem
cl_tim::read(class cl_memory_cell *cell)
{
  ....
  if (a == idx.pscrl)
    v= prescaler_preload && 0xff;
  else if (a == idx.pscrh)
    v= (prescaler_preload >> 8) & 0xff;
  ....
}

Предупреждение PVS-Studio: V560 [CWE-571] Часть условного выражения всегда истинна: ​​0xff. timer.cc 198

Это классическая опечатка. Вместо оператора & они написали &&. В результате мы получаем логическое значение вместо значения младшего байта, которое позже становится 0 или 1.

Мне очень нравятся такие ошибки :) Они такие милые. Да, у меня странное понятие о красоте :).

Фрагмент N3

cl_timer2::init(void)
{
  cl_timer0::init();
  cell_rcap2l= sfr->get_cell(RCAP2L);
  cell_rcap2h= sfr->get_cell(RCAP2H);
  if (sfr)
    bit_t2ex= sfr->read(P1) & bmT2EX;
  return(0);
}

Предупреждение PVS-Studio: V595 [CWE-476] Указатель sfr использовался до того, как был проверен на nullptr. Проверить строки: 54, 56. timer2.cc 54

Вначале активно используется указатель sfr. Потом оказывается, что он может быть нулевым.

Таких ошибок явно несколько. Некоторые другие места:

  • V595 [CWE-476] Указатель value.string.string использовался до проверки на соответствие nullptr. Проверить строки: 244, 246. arg.cc 244
  • V595 [CWE-476] Указатель app использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 668, 675. command.cc 668
  • V595 [CWE-476] Указатель param_str использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 245, 247. command.cc 245
  • V595 [CWE-476] Указатель uc использовался до того, как он был проверен на соответствие nullptr. Проверить строки: 342, 343. flash.cc 342

Последний 4-й фрагмент

u32_t  rabbit_mmu::logical_addr_to_phys( u16_t logical_addr ) {
  u32_t  phys_addr = logical_addr;
  unsigned     segnib = logical_addr >> 12;
  
  if (segnib >= 0xE000)
  {
    phys_addr += ((u32_t)xpc) << 12;
  }
  ....
}

Предупреждение PVS-Studio: V547 [CWE-570] Выражение ‘segnib ›= 0xE000’ всегда ложно. inst_r2k.cc 42

Это странный код, так как условие никогда не будет выполнено. Переменная logical_addr имеет тип unsigned short (16 байтов) и может иметь максимальное значение 0xFFFF. После сдвига на 12 позиций это максимальное значение превратится в 0x000F, что явно меньше 0xE000. Видимо, тут что-то напутано, и рассчитать или проверить хотели совсем другое.

Заключение

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

Предлагаю скачать и попробовать анализатор PVS-Studio для проверки проектов на C, C++, C# и Java: https://www.viva64.com/ru/pvs-studio-download/.

Кроме того, хочу напомнить, что скоро начнется бета-тестирование анализатора C# под Linux и macOS: https://www.viva64.com/ru/b/0716/.