Вебинар: C# разработка и статический анализ: в чем практическая польза? - 18.11
Cfront это компилятор для С++, существующий примерно с 1983 года и разработанный Бьёрном Страуструпом. В то время он был известен как "C с классами". Cfront имел полноценный парсер, таблицы символов, строил дерево для каждого класса, функции и т.д. Cfront был основан на CPre. Cfront определял развитие языка приблизительно до 1990г. Многие неясные моменты, имеющие место в С++, связаны с ограничениями реализации Cfront. Причина в том, что Cfront осуществлял трансляцию с C++ в C. Одним словом, Cfront - это священный артефакт для любого C++ программиста. И я просто не мог пройти мимо, не проверив этот проект.
На идею проверить 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 причинам:
Однако хватит слов. Наши читатели собрались здесь, чтобы увидеть хоть одну ошибку самого Страуструпа. Давайте смотреть код.
Первый фрагмент
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 сложно переоценить. Он оказал влияние на развитие целой отрасли программирования и подарил миру вечно живой и развивающийся язык C++. Выражаю Бьёрну благодарность за всю проделанную им работу в создании С++. Спасибо. Мне в свою очередь было приятно хотя бы "постоять рядом" с Cfront.
Спасибо всем читателям, и хочу пожелать поменьше багов.
0