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

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

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

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

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

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

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


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

>
>
>
Занимательная археология. Или PVS-Studi…

Занимательная археология. Или PVS-Studio проверяет Microsoft Word 1.1a

02 Апр 2014

Недавно компания Microsoft сделала подарок всем программистам, которые хотят покопаться в чем-то интересном. Microsoft открыли исходный код MS-DOS v 1.1, v 2.0 и Word for Windows 1.1a. Операционная система MS-DOS написана на ассемблере, и к ней анализатор не применим. А вот Word написан на языке Си. Исходным кодам Word 1.1a почти 25 лет, однако нам кое-как удалось их проверить. Конечно, никакой практической ценности в этой проверке нет. Just for fun.

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

0245_Checking_Microsoft_Word_ru/image1.png

Где поживиться исходниками

Возможно, многим будет интересна не сколько эта статья, а сам факт, что можно скачать исходные коды MS-DOS v 1.1, v 2.0 и Word for Windows 1.1a. Тем, кому интересно самим покопаться в исходных кодах, отправляю к первоисточнику.

Пресс-релиз: Computer History Museum Makes Historic MS-DOS and Word for Windows Source Code Available to the Public.

Проверка Word 1.1a

0245_Checking_Microsoft_Word_ru/image2.png

Рисунок 1. Word for Windows 1.1a (нажмите на картинку для увеличения).

Word for Windows 1.1a был выпущен в 1990 году. 25 марта 2014 код этого продукта стал доступен публике. Word был и остаётся флагманским продуктом компании Microsoft. Мне и многим другим интересно посмотреть на внутренности программного продукта, который так сильно поспособствовал коммерческим успехам компании Microsoft.

Я решил проверить код Word 1.1a с помощью нашего инструмента PVS-Studio. Это статический анализатор Си/Си++ кода. Естественно, это не так просто. Анализатор рассчитан на работу с проектами, разрабатываемыми как минимум в Visual Studio 2005. А сейчас предо мной исходники на языке Си, которым более 20 лет. Можно сказать, что это доисторические времена. По крайней мере, тогда не существовало стандарта языка Си. Каждый компилятор был сам по себе. К счастью, в исходных кодах Word 1.1a не оказалось каких-то необычных моментов и использования большого количества нестандартных расширений компилятора.

Для анализа необходимы препроцессированные файлы (*.i). Имея препроцессированные файлы, можно воспользоваться инструментом PVS-Studio Standalone. С его помощью можно выполнить анализ и изучить диагностические сообщения. Конечно, анализатор не рассчитан на анализ 16-битных программ. Но этих результатов анализа будет вполне достаточно для удовлетворения любопытства. Внимательно анализировать проект 24 летней давности нет никакого практического смысла.

Итак, основная загвоздка состояла в том, как получить препроцессированные файлы. Я попросил своего коллегу поколдовать в этом направлении. Он подошёл к решению весьма творчески. Он выполнил препроцессирование с помощью GCC 4.8.1. Вряд ли кто-то ещё так издевался над исходниками Word 1.1. Использовать GCC - надо ведь было такое придумать. Фантазёр.

Самое интересное, что вышло вполне удачно. Была написана маленькая утилита, которая запускала препроцессирование с помощью GCC 4.8.1 на каждый файл из директории, в которой он лежал. По мере вывода ошибок, связанных с включением заголовочных файлов, в параметры запуска добавлялись ключи -I с путём до нужных файлов. Парочка ненайденных заголовочных файлов были созданы пустыми. Все остальные проблемы раскрытия #include были связаны с включением ресурсов, поэтому были закомментированы. При препроцессировании определялся макрос WIN, т.к. в коде есть ветка для WIN и MAC.

Дальше в дело вступил PVS-Studio Standalone и ваш покорный слуга. Я выписал подозрительные фрагменты кода и готов вам их показать. Но вначале ещё кое-что о проекте.

Разное о коде Word 1.1a

Самые сложные функции

Самая большая цикломатическая сложность у следующих функций:

  • CursUpDown - 219;
  • FIdle - 192;
  • CmdDrCurs1 - 142.

#ifdef WIN23

Просматривая исходные коды и встретив "#ifdef WIN23", я заулыбался. И даже выписал это место. Я подумал, что это опечатка и должно быть написано #ifdef WIN32.

Когда я увидел WIN23 второй раз я засомневался. А потом вдруг осознал, что я смотрю исходники 24 летней давности. WIN23 означает версию Windows 2.3.

Суровые времена

В коде мне попалась вот такая интересная строка.

Assert((1 > 0) == 1);

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

Конечно, если считать K&R стандартом, то по идее условие ((1 > 0) == 1) всегда выполняется. Но K&R это был лишь стандарт де-факто и не более. Это проверка на адекватность компилятора.

Результаты проверки

Теперь поговорим о подозрительных местах, найденных мною в коде. Думаю, ради этого вы и читаете эту статью. Приступим.

Бесконечный цикл

void GetNameElk(elk, stOut)
ELK elk;
unsigned char *stOut;
{
  unsigned char *stElk = &rgchElkNames[mpelkichName[elk]];
  unsigned cch = stElk[0] + 1;

  while (--cch >= 0)
    *stOut++ = *stElk++;
}

Предупреждение PVS-Studio: V547 Expression '-- cch >= 0' is always true. Unsigned type value is always >= 0. mergeelx.c 1188

Цикл "while (--cch >= 0)" никогда не остановится. Переменная 'cch' имеет тип unsigned. Значит, сколько ни уменьшай эту переменную, она всегда останется >= 0.

Выход за границу массива из-за опечатки

uns rgwSpare0 [5];

DumpHeader()
{
  ....
  printUns ("rgwSpare0[0]   = ", Fib.rgwSpare0[5], 0, 0, fTrue);
  printUns ("rgwSpare0[1]   = ", Fib.rgwSpare0[1], 1, 1, fTrue);
  printUns ("rgwSpare0[2]   = ", Fib.rgwSpare0[2], 0, 0, fTrue);
  printUns ("rgwSpare0[3]   = ", Fib.rgwSpare0[3], 1, 1, fTrue);
  printUns ("rgwSpare0[4]   = ", Fib.rgwSpare0[4], 2, 2, fTrue);
  ....
}

Предупреждение PVS-Studio: V557 Array overrun is possible. The '5' index is pointing beyond array bound. dnatfile.c 444

Как-то так получилось, что в первой строке написано: Fib.rgwSpare0[5]. Это неправильно. В массиве всего 5 элементов, а значит максимальный индекс должен быть равен 4. Значение '5' это результат опечатки. По всей видимости в первой строке должен был использоваться нулевой индекс:

printUns ("rgwSpare0[0]   = ", Fib.rgwSpare0[0], 0, 0, fTrue);

Неинициализированная переменная

FPrintSummaryInfo(doc, cpFirst, cpLim)
int doc;
CP cpFirst, cpLim;
{
  int fRet = fFalse;
  int pgnFirst = vpgnFirst;
  int pgnLast = vpgnLast;
  int sectFirst = vsectFirst;
  int sectLast = sectLast;
  ....
}

Предупреждение PVS-Studio: V573 Uninitialized variable 'sectLast' was used. The variable was used to initialize itself. print2.c 599

Переменная 'sectLast' присваивается сама себе:

int sectLast = sectLast;

Кажется, для инициализации должна была быть использована переменная 'vsectLast':

int sectLast = vsectLast;

Нашлось ещё одна идентичная ошибка. Видимо последствие Copy-Paste:

V573 Uninitialized variable 'sectLast' was used. The variable was used to initialize itself. print2.c 719

Неопределённое поведение

CmdBitmap()
{
  static int  iBitmap = 0;
  ....
  iBitmap = ++iBitmap % MAXBITMAP;
}

Предупреждение PVS-Studio: V567 Undefined behavior. The 'iBitmap' variable is modified while being used twice between sequence points. ddedit.c 107

Не знаю, как к такому коду относились 20 лет назад. Но сейчас это считается хулиганством, так как приводит к неопределённому поведению.

Аналогично:

  • V567 Undefined behavior. The 'iIcon' variable is modified while being used twice between sequence points. ddedit.c 132
  • V567 Undefined behavior. The 'iCursor' variable is modified while being used twice between sequence points. ddedit.c 150

Неудачный вызов функции printf()

ReadAndDumpLargeSttb(cb,err)
  int     cb;
  int     err;
{
  ....
  printf("\n - %d strings were read, "
         "%d were expected (decimal numbers) -\n");
  ....
}

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

Функция printf(), это функция с переменным количеством аргументов. Ей можно передать аргументы, а можно и не передать. Вот здесь про аргументы забыли, в результате чего будет распечатан мусор.

Неинициализированные указатели

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

main(argc, argv)
int argc;
char * argv [];
{
  FILE * pfl;
  ....
  for (argi = 1; argi < argc; ++argi)
  {
    if (FWild(argv[argi]))
    {
      FEnumWild(argv[argi], FEWild, 0);
    }
    else
    {
      FEWild(argv[argi], 0);
    }

    fclose(pfl);
  }
  ....
}

Предупреждение PVS-Studio: V614 Uninitialized pointer 'pfl' used. Consider checking the first actual argument of the 'fclose' function. eldes.c 87

Переменная 'pfl' не инициализируется до цикла и в самом цикле. Зато много раз вызывается функция fclose(pfl). Впрочем, всё это вполне могло успешно работать. Функция вернёт статус ошибки, и программа продолжит свою работу.

А вот ещё одна опасная функция. Скорее всего, её вызов приведёт к аварийному завершению программы.

FPathSpawn( rgsz )
char *rgsz[];
{ /* puts the correct path at the beginning of rgsz[0]
     and calls FSpawnRgsz */
  char *rgsz0;

  strcpy(rgsz0, szToolsDir);
  strcat(rgsz0, "\\");
  strcat(rgsz0, rgsz[0]);
  return FSpawnRgsz(rgsz0, rgsz);
}

Предупреждение PVS-Studio: V614 Uninitialized pointer 'rgsz0' used. Consider checking the first actual argument of the 'strcpy' function. makeopus.c 961

Указатель ' rgsz0' ничем не инициализируется. Это не мешает начать копировать в него строку.

Опечатка в условии

....
#define wkHdr    0x4000
#define wkFtn    0x2000
#define wkAtn    0x0008
....
#define wkSDoc    (wkAtn+wkFtn+wkHdr)

CMD CmdGoto (pcmb)
CMB * pcmb;
{
  ....
  int wk = PwwdWw(wwCur)->wk;
    if (wk | wkSDoc)
      NewCurWw((*hmwdCur)->wwUpper, fTrue);
  ....
}

Предупреждение PVS-Studio: V617 Consider inspecting the condition. The '(0x0008 + 0x2000 + 0x4000)' argument of the '|' bitwise operation contains a non-zero value. dlgmisc.c 409

Условие (wk | wkSDoc) всегда истинно. На самом деле, здесь, скорее всего, хотели написать:

if (wk & wkSDoc)

В общем, перепутали оператор | и &.

И под конец длинный, но простой пример

int TmcCharacterLooks(pcmb)
CMB * pcmb;
{
  ....
  if (qps < 0)
  {
    pcab->wCharQpsSpacing = -qps;
    pcab->iCharIS = 2;
  }
  else  if (qps > 0)
  {
    pcab->iCharIS = 1;
  }
  else
  {
    pcab->iCharIS = 0;
  }
  ....
  if (hps < 0)
  {
    pcab->wCharHpsPos = -hps;
    pcab->iCharPos = 2;
  }
  else  if (hps > 0)
  {
    pcab->iCharPos = 1;
  }
  else
  {
    pcab->iCharPos = 1;
  }
  ....
}

Предупреждение PVS-Studio: V523 The 'then' statement is equivalent to the 'else' statement. dlglook1.c 873

Когда работают с переменной 'qps', то записывают в 'pcab->iCharIS' следующие значения: 2, 1, 0.

Аналогично работают с переменной 'hps'. Но при этом в переменную 'pcab->iCharPos' помещаются подозрительные числа: 2, 1, 1.

Скорее всего, это опечатка. В самом конце, наверное, следовало использовать ноль.

Заключение

Найдено совсем немного странных мест. Причины две. Во-первых, код мне показался написан качественно и весьма понятно. Во-вторых, анализ был все-таки неполноценным. Учить же анализатор особенностям старого Си нет практической надобности.

Надеюсь, я подарил вам несколько минут интересного чтения. Спасибо за внимание. И попробуйте анализатор PVS-Studio на своём коде.

Популярные статьи по теме
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

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

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

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

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
Статический анализ как часть процесса разработки Unreal Engine

Дата: 27 Июн 2017

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

Проект Unreal Engine развивается - добавляется новый код и изменятся уже написанный. Неизбежное следствие развития проекта - появление в коде новых ошибок, которые желательно выявлять как можно раньш…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

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

Краткое описание технологий, используемых в инструменте PVS-Studio, которые позволяют эффективно обнаруживать большое количество паттернов ошибок и потенциальных уязвимостей. Статья описывает реализа…
PVS-Studio ROI

Дата: 30 Янв 2019

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

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Эффект последней строки

Дата: 31 Май 2014

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

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Дата: 31 Июл 2017

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

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода)…
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

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

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

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

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…
Любите статический анализ кода!

Дата: 16 Окт 2017

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

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

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

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