Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
К тридцатилетию первого C++ компилятора…

К тридцатилетию первого C++ компилятора: ищем ошибки в Cfront

Cfront это компилятор для С++, существующий примерно с 1983 года и разработанный Бьёрном Страуструпом. В то время он был известен как "C с классами". Cfront имел полноценный парсер, таблицы символов, строил дерево для каждого класса, функции и т.д. Cfront был основан на CPre. Cfront определял развитие языка приблизительно до 1990г. Многие неясные моменты, имеющие место в С++, связаны с ограничениями реализации Cfront. Причина в том, что Cfront осуществлял трансляцию с C++ в C. Одним словом, Cfront - это священный артефакт для любого C++ программиста. И я просто не мог пройти мимо, не проверив этот проект.

0355_CFront_ru/image1.png

Введение

На идею проверить Cfront меня натолкнула заметка, приуроченная к 30-летию первой Release версии этого компилятора: "30 YEARS OF C++". Мы связались с Бьёрном Страуструпом, чтобы заполучить исходные коды Cfront. Я почему-то думал, что достать их будет целая история. Оказалось, всё просто. Эти исходники лежат в открытом доступе по адресу http://www.softwarepreservation.org/projects/c_plus_plus/ и доступны всем желающим.

Для проверки была выбрана первая коммерческая версия Cfront, выпущенную в октябре 1985 года. Ведь именно ей исполнилось 30 лет.

Бьёрн предупредил нас, что с проверкой может оказаться не всё так просто:

Please remember this is *very* old software designed to run on a 1MB 1MHz machine and also used on original PCs (640KB). It was also done by one person (me) as only part of my full time job.

И действительно. Вот так просто взять и проверить проект оказалось невозможным. Например, в те времена для отделения имени класса от имени функции использовалось не четыре точки (::), а просто точка (.). Пример:

inline Pptr type.addrof() { return new ptr(PTR,this,0); }

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

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

  • Проект имеет небольшой размер. Всего 100 KLOC в 143 файлах.
  • Код качественный.
  • Анализатор PVS-Studio все-таки далеко не всё смог проверить.

"Это все слова. Покажи мне код." (c) Линус Торвальдс

Однако хватит слов. Наши читатели собрались здесь, чтобы увидеть хоть одну ошибку самого Страуструпа. Давайте смотреть код.

Первый фрагмент

typedef class classdef * Pclass;

#define PERM(p) p->permanent=1

Pexpr expr.typ(Ptable tbl)
{
  ....
  Pclass cl;
  ....
  cl = (Pclass) nn->tp;
  PERM(cl);
  if (cl == 0) error('i',"%k %s'sT missing",CLASS,s);
  ....
}

Предупреждение PVS-Studio: V595 The 'cl' pointer was utilized before it was verified against nullptr. Check lines: 927, 928. expr.c 927

Указатель 'cl' может быть равен NULL. Об этом свидетельствует проверка if (cl == 0). Беда в том, что ещё до этой проверки этот указатель разыменовывается. Это происходит в макросе PERM.

Т.е. если раскрыть макрос, то получаем:

cl = (Pclass) nn->tp;
cl->permanent=1
if (cl == 0) error('i',"%k %s'sT missing",CLASS,s);

Второй фрагмент

То же самое. Разыменовали указатель, и только потом его проверили:

Pname name.normalize(Pbase b, Pblock bl, bit cast)
{
  ....
  Pname n;
  Pname nn;
  TOK stc = b->b_sto;
  bit tpdf = b->b_typedef;
  bit inli = b->b_inline;
  bit virt = b->b_virtual;
  Pfct f;
  Pname nx;
  if (b == 0) error('i',"%d->N.normalize(0)",this);
  ....
}

Предупреждение PVS-Studio: V595 The 'b' pointer was utilized before it was verified against nullptr. Check lines: 608, 615. norm.c 608

Третий фрагмент

int error(int t, loc* lc, char* s ...)
{
  ....
  if (in_error++)
    if (t!='t' || 4<in_error) {
      fprintf(stderr,"\nUPS!, error while handling error\n");
      ext(13);
    }
  else if (t == 't')
    t = 'i';
  ....
}

Предупреждение PVS-Studio: V563 It is possible that this 'else' branch must apply to the previous 'if' statement. error.c 164

Не знаю, есть здесь ошибка или нет, но код оформлен неправильно. 'else' относится к ближайшему 'if'. Поэтому код работает не так, как выглядит. Если отформатировать его правильно, то получится:

if (in_error++)
  if (t!='t' || 4<in_error) {
    fprintf(stderr,"\nUPS!, error while handling error\n");
    ext(13);
  } else if (t == 't')
    t = 'i';

Четвертый фрагмент

extern
genericerror(int n, char* s)
{
  fprintf(stderr,"%s\n",
          s?s:"error in generic library function",n);
  abort(111);
  return 0;
};

Предупреждение PVS-Studio: V576 Incorrect format. A different number of actual arguments is expected while calling 'fprintf' function. Expected: 3. Present: 4. generic.c 8

Обратите внимание на format specifiers: "%s". Будет распечатана строка. А вот переменная 'n' осталась не при деле.

Прочее

К сожалению (или к счастью), больше ничего похожего на настоящие ошибки я показать не могу. Анализатор выдал ряд предупреждений на код, который хотя и заслуживает внимание, но не является опасным. Например, анализатору не нравятся имена следующих глобальных переменных:

extern int Nspy, Nn, Nbt, Nt, Ne, Ns, Nstr, Nc, Nl;

Предупреждение PVS-Studio: V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'Nn' variable. cfront.h 50

Или, например, для распечатки значений указателей функцией fprintf() использует спецификатор "%i". В современной версии языка для этого служит "%p". Но как я понимаю, 30 лет назад никакого "%p" ещё не было, и код совершенно корректен.

Интересные наблюдения

Указатель this

Обратил внимание, что раньше с 'this' работали на порядок более смело и грубо. Пара примеров на эту тему:

expr.expr(TOK ba, Pexpr a, Pexpr b)
{
  register Pexpr p;

  if (this) goto ret;
  ....
  this = p;
  ....
}

inline toknode.~toknode()
{
  next = free_toks;
  free_toks = this;
  this = 0;
}

Как видите, в те времена не считалось чем-то запретным, взять и поменять значение 'this'. Сейчас запрещается не только менять указатель, но и даже потеряли смысл сравнения this с nullptr.

This is the place for paranoia

Как говорится, ни в чем нельзя быть уверенным. Понравился вот такой фрагмент кода, на который я натолкнулся:

/* this is the place for paranoia */
if (this == 0) error('i',"0->Cdef.dcl(%d)",tbl);
if (base != CLASS) error('i',"Cdef.dcl(%d)",base);
if (cname == 0) error('i',"unNdC");
if (cname->tp != this) error('i',"badCdef");
if (tbl == 0) error('i',"Cdef.dcl(%n,0)",cname);
if (tbl->base != TABLE) error('i',"Cdef.dcl(%n,tbl=%d)",
                              cname,tbl->base);

Комментарий Бьёрна Страуструпа

  • Cfront был создан на основе Cpre, но при этом полностью переписан. От Cpre в коде Cfront не осталось ни строчки.
  • В ошибке use-before-test-of-0 (использование до проверки на 0), конечно, нет ничего хорошего, однако, что любопытно, конфигурация, на которой я преимущественно работал (машина DEC и ОС Research Unix), реализовали защиту от записи нулевой страницы (и здесь тоже), так что этот баг не смог бы сработать, не будучи обнаруженным.
  • С багом (если это действительно баг) с if-then-else вышло необычно. Я посмотрел исходный код: это не просто опечатка, а именно ошибка. Однако, что интересно, она никак не влияет на результат: будет лишь небольшая разница в сообщении об ошибке, которое выведется перед завершением. Неудивительно, что я ее не заметил.
  • Да, мне следовало использовать более удобочитаемые имена. Просто изначально я не рассчитывал на то, что программу в течение многих лет будут поддерживать другие люди (и еще я плохо печатаю).
  • Да, спецификаторов %p в те времена еще не было.
  • Да, правила для "this" поменялись.
  • В основном цикле компилятора использовался "параноидальный тест". Я исходил из соображений, что, случись что-то с ПО или железом, один из этих тестов будет провален. Как минимум однажды он выявил последствия одного бага в генераторе кода, который использовался для сборки Cfront. Я считаю, что все серьезные приложения должны использовать такой "параноидальный тест" для отлова "невозможных" ошибок.

Выводы

Значение Cfront сложно переоценить. Он оказал влияние на развитие целой отрасли программирования и подарил миру вечно живой и развивающийся язык C++. Выражаю Бьёрну благодарность за всю проделанную им работу в создании С++. Спасибо. Мне в свою очередь было приятно хотя бы "постоять рядом" с Cfront.

Спасибо всем читателям, и хочу пожелать поменьше багов.

Последние статьи:

Опрос:

Популярные статьи по теме
Примеры ошибок, которые может обнаружить PVS-Studio в коде LLVM 15.0

Дата: 25 Окт 2022

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

Компиляторы развиваются и выдают всё больше предупреждений. Остаются ли преимущества от использования статических анализаторов кода, таких как PVS-Studio? Да, так как анализаторы тоже развиваются. Пе…
Выявляем ошибки в релизе LLVM 13.0.0

Дата: 08 Окт 2021

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

Задача коммерческих статических анализаторов выполнять более глубокий и полный анализ кода, чем компиляторы. Давайте посмотрим, что смог обнаружить PVS-Studio в исходном коде проекта LLVM 13.0.0.
Проверка Clang 11 с помощью PVS-Studio

Дата: 27 Окт 2020

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

Время от времени нам приходится писать статьи о проверке очередной версии какого-то компилятора. Это неинтересно. Однако, как показывает практика, если этого долго не делать, люди начинают сомневатьс…
PVS-Studio теперь в Compiler Explorer!

Дата: 06 Июл 2020

Автор: Георгий Грибков

Совсем недавно произошло знаменательное событие: PVS-Studio появился в Compiler Explorer! Теперь вы можете быстро и легко проанализировать код на наличие ошибок прямо на сайте godbolt.org (Compiler E…
Анализатор PVS-Studio на сайте godbolt.org (Compiler Explorer) и предостережение

Дата: 08 Июн 2020

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

Мы добавили возможность экспериментировать со статическим анализатором кода PVS-Studio на сайте godbolt.org (Compiler Explorer). Поддерживается анализ C и C++ кода. Уверены, что это интересный и очен…

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

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо