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

Билл Торпи недавно написал в блоге сообщение под названием Even Mo 'Static, в котором поделился своим мнением о результатах тестирования анализаторов Cppcheck и PVS-Studio в проекте itc-benchmarks, который представляет собой набор тестов для статического анализа. компании Toyota ITC.

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

Я считаю, что это неправильный вывод. Я считаю, что наш анализатор PVS-Studio в несколько раз мощнее Cppcheck. Что ж, это даже не «мнение» - это то, что я знаю точно!

Однако, поскольку для стороннего наблюдателя не очевидно, что PVS-Studio в десять раз лучше, чем Cppcheck, для этого должна быть причина. Я решил взглянуть на этот проект, itc-benchmarks, и выяснить, почему PVS-Studio не работает с максимальной эффективностью на этой кодовой базе.

Чем больше я копал, тем большее раздражение чувствовал. Был один конкретный пример, который сводил меня с ума, и я сейчас расскажу вам о нем. В заключение я должен сказать следующее: у меня нет претензий к Биллу Торпи. Он написал хорошую, честную статью. Спасибо, Билл! Но у меня есть претензии к Toyota ITC. Я лично считаю, что их кодовая база - дерьмо. Да, это резкое заявление, но я считаю, что у меня достаточно знаний и опыта, чтобы обсуждать статические анализаторы кода и способы их оценки. На мой взгляд, тесты itc-benchmarks не могут использоваться для адекватной оценки диагностических возможностей инструментов.

А вот и испытание, которое меня убило.

Это тест на разыменование нулевого указателя:

void null_pointer_001 ()
{
  int *p = NULL;
  *p = 1; /*Tool should detect this line as error*/
          /*ERROR:NULL pointer dereference*/
}

Анализатор Cppcheck сообщает об ошибке в этом коде:

Null pointer dereference: p

Анализатор PVS-Studio молчит, хотя для таких случаев у него есть диагностика V522.

Значит ли это, что PVS-Studio хуже диагностирует этот пример, чем Cppcheck? Нет, как раз наоборот: так лучше!

PVS-Studio понимает, что этот код был написан специально и ошибки в нем нет.

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

void GpuChildThread::OnCrash() {
  LOG(INFO) << "GPU: Simulating GPU crash";
  // Good bye, cruel world.
  volatile int* it_s_the_end_of_the_world_as_we_know_it = NULL;
  *it_s_the_end_of_the_world_as_we_know_it = 0xdead;
}

Вот почему мы включили ряд исключений в диагностическое правило PVS-Studio V522, чтобы он не сходил с ума по такому коду. Анализатор понимает, что null_pointer_001 - искусственная функция; просто нет ошибок, связанных с присвоением нуля указателю и последующим немедленным разыменованием его в реальных функциях. Само название функции также является знаком для анализатора, что «нулевой указатель» здесь не случаен.

Для таких случаев в диагностике V522 есть исключение A6. Это исключение, к которому подпадает синтетическая функция null_pointer_001. Это описание исключения A6:

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

  • ошибка
  • по умолчанию
  • сбой
  • ноль
  • тест
  • нарушение
  • бросить
  • исключение

Перед разыменованием переменной присваивается 0 на одну строку раньше.

Рассматриваемый синтетический тест полностью подходит под это описание. Во-первых, имя функции содержит слово «null». Во-вторых, переменной присваивается ноль ровно на одну строку раньше. Исключение выявило нереальный код, который на самом деле является синтетическим тестом.

Именно за эти тонкие детали я не люблю синтетические тесты!

Это не единственная жалоба на itc-benchmarks. Например, в том же файле есть еще один тест:

void null_pointer_006 ()
{
  int *p;
  p = (int *)(intptr_t)rand();
  *p = 1; /*Tool should detect this line as error*/
          /*ERROR:NULL pointer dereference*/
}

Функция rand может вернуть 0, который затем превратится в NULL. Анализатор PVS-Studio пока не знает, что может вернуть rand, поэтому у него нет никаких подозрений по поводу этого кода.

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

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

Я хочу, чтобы разработчики поняли, что пример с rand на самом деле ничего не показывает. Это синтетика, совершенно надуманная. Никто так не пишет программы; на самом деле таких ошибок нет.

Кстати, если функция rand вернет 1400 вместо 0, лучше не будет. Такой указатель ни в коем случае нельзя разыменовать. Итак, разыменование нулевого указателя - это какой-то странный частный случай совершенно некорректного кода, который был просто придуман авторами пакета и который вы никогда не увидите в реальности.

Я знаю настоящие проблемы программирования. Это, в том числе, опечатки, и наш инструмент регулярно выявляет их сотни с помощью, скажем, диагностического V501. Забавно, но я не нашел в itc-benchmarks теста, который проверял бы, могут ли инструменты определить шаблон опечатки if (a.x == a.x). Ни одного теста!

Оказывается, itc-benchmarks игнорирует возможности анализатора по поиску опечаток, в то время как наши читатели наверняка знают, насколько широко распространены дефекты этого типа. И в этом проекте есть тестовые примеры, которые я считаю глупыми и никогда не встречающиеся в реальных программах. Я не могу представить, чтобы в реальном серьезном проекте я наткнулся на код, подобный приведенному ниже, что привело бы к переполнению массива:

void overrun_st_014 ()
{
  int buf[5];
  int index;
  index = rand();
  buf[index] = 1; /*Tool should detect this line as error*/
                  /*ERROR: buffer overrun */
  sink = buf[idx];
}

Единственный тип программ, который вы, вероятно, можете найти, - это упражнения студентов по программированию.

В то же время я знаю, что в серьезном проекте вы, скорее всего, столкнетесь со следующей опечаткой:

return (!strcmp (a->v.val_vms_delta.lbl1,
                 b->v.val_vms_delta.lbl1)
        && !strcmp (a->v.val_vms_delta.lbl1,
                    b->v.val_vms_delta.lbl1));

Эту ошибку PVS-Studio обнаружил в коде компилятора GCC: одни и те же строки сравниваются дважды.

Итак, набор включает тесты для диагностики экзотического кода с помощью rand, но нулевые тесты на классические опечатки.

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

Добро пожаловать на установку и опробование мощнейшего анализатора кода PVS-Studio.

Ссылки: