Стилистический Анализатор: Синхронизация порядка объявлений и определений функций

от автора

Пролог

У нас в организации есть правило оформления исходников, которое звучит так:

Порядок объявления функций должен совпадать с порядком определения функций.

В чём проблема?

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

Но раз это требование тут существует, то его, как ни крути, приходится выставлять. При этом возникает целый ряд вытекающих проблем

  1. У компилятора GCC нет таких ключей, которые бы выявили разный порядок в объявлении и определении функций.

  2. В статическом анализаторе CppCheck нет таких ключей, чтобы выявить разный порядок в объявлении и определении функций

  3. В платном статическом анализаторе Understand (scitools) тоже нет таких ключей, чтобы выявить разный порядок в объявлении и определении функций

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

Поэтому я и решил написать такую утилиту. В этом есть потребность. И у меня это получилось.

Постановка задачи

Написать консольную утилиту, которая будет сообщать программисту о нарушениях соответствия последовательности объявления и определения функций в программах на Си.

Работать с утилитой должно быть просто. Буквально даешь ей *.с файл,

prototype_check.exe cgp dds.c

а утилита сама находит одноименный *.h файл, вычитывает последовательности объявления и вычитывает последовательности определения функций, сравнивает их и сигнализирует об ошибке в виде return кода (0 — успех 1 ошибка).

Терминология

Прежде чем двигаться дальше надо кое‑что запомнить.

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

СygWin — набор Unix утилит для операционной системы Windows.

Что надо из софвера?

Я собираюсь решить эту задачу самым обыкновенным инструментарием из CygWin

Название утилиты

Назначение

1

сtags

Создает файл со списком тэгов для данного языка программирования. Эдакий индексатор исходных кодов.

2

awk/gawk

программируемый анализатор текстовых строчек

3

sed

утилита для авто удаления или авто замены строчек в текстовых файлах

5

cmp

утилита для сравнения текстовых файлов

6

rm

утилита для удаления файлов

Каков план?

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

Реализация

Есть одна старая и очень полезная утилита. Называется ctags. Это по сути анализатор токенов в разных языках программирования. В частности получить список функций внутри *.с файла можно как раз утилитой сtags. Утилиту сtags можно извлечь из CygWin

После установки ctags надо прописать путь к утилитам СygWin (C:\cygwin64\bin) в переменную PATH. После этого утилита where должна находить утилиту ctags

C:\Users\Name>where ctags C:\cygwin64\bin\ctags.exe

С какими ключами надо запускать ctags?

Ключ утилиты

Действие ключа

1

—sort=no

Не сортировать строчки в выходной таблице с отчётом

2

-fxxxxx

Писать отчет в файл xxxxx

3

-x —c-types=f

Сгенерировать отчет по функциям языка программирования Си

Фаза 1: Получить список всех функций в Си файле

После отработки по *.с файлу с такими ключами появляется вот такой отчет в виде таблицы

вывод утилиты ctags.exe для *.с файла
DDS_GetNode      function    151 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c DDS_HANDLE* DDS_GetNode(const U8 num) DDS_CalcSinSample function    187 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c FLOAT32 DDS_CalcSinSample(const U64 upTimeUs, DDS_Ctrl         function    216 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Ctrl(const U8 num, DDS_Init         function    249 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Init(void) DDS_InitOne      function    289 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_InitOne(const U8 num) DDS_Play         function    339 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Play(const U8 num, DDS_Play1kHz     function    388 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Play1kHz(const U8 num, DDS_Proc         function    414 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Proc(void) DDS_SetArray     function    454 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetArray(const U8 num, DDS_SetFence     function    513 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetFence(const U8 num, DDS_SetFramePerSec function    546 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetFramePerSec(const U8 num, DDS_SetPattern   function    572 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetPattern(const U8 num, DDS_SetPwm       function    602 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetPwm(const U8 num, DDS_SetSaw       function    641 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetSaw(const U8 num, DDS_SetSin       function    676 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_SetSin(const U8 num, DDS_Stop         function    717 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c STD_RESULT DDS_Stop(const U8 num) DDS_GetConfig    function    753 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c const DDS_CONFIG* DDS_GetConfig(const U8 num) DDS_ProcOne      function    791 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_ProcOne(const U8 num) DDS_OnOffToState function    838 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static DDS_STATE DDS_OnOffToState(const U8 onOff) DDS_IsValidPlayer function    868 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_IsValidPlayer(const DDS_PLAYER player) DDS_IsValidSignal function    919 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_IsValidSignal(const DDS_SIGNAL ddsSignal) DDS_IsValidFramePattern function    967 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_IsValidFramePattern(const DDS_SAMPLE_PATTERN samplePattern) DDS_IsValidConfig function   1005 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_IsValidConfig(const DDS_CONFIG* const Config) DDS_IsValidSampleBitness function   1119 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_IsValidSampleBitness(const U8 sampleBitness) DDS_CalcMaxTimeNs function   1156 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static U32 DDS_CalcMaxTimeNs(DDS_HANDLE* const Node, DDS_CalcOneSampleLowLevel function   1182 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static DDS_SAMPLE_TYPE DDS_CalcOneSampleLowLevel(DDS_HANDLE* const Node, DDS_CalcStoreOneSampleLowLevel function   1231 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static STD_RESULT DDS_CalcStoreOneSampleLowLevel(DDS_HANDLE* const Node, DDS_PlayerToI2sNum function   1275 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static S16 DDS_PlayerToI2sNum(const DDS_PLAYER player) DDS_SetValidFreq function   1317 C:/projects/code_base_workspace/era_test/source/third_party/computing/dds/dds.c static FLOAT32 DDS_SetValidFreq(const FLOAT32 frequencyHz) 

Как можно убедиться, порядок перечисления функций в отчете совпадает с порядком их определения в исходном Си файле.

Фаза 2: Удалить Static функции

Из отчета надо удалить строчки которые отвечают за локальные функции. Это можно сделать утилитой sed

sed -i '/static/d' cTagFunctionReport.txt

Фаза 3: Выделить только имена функций

Из отчета надо удалить всяческую вспомогательную информацию: номер строчки, путь к файлу, кусок текста. Это можно сделать утилитой awk. Вот так.

gawk '{print $1}' cTagFunctionReport.txt > ctags_function_report_c_functions.txt

после этого получается чистый файл со списком имен функций.

список функций с сохранением порядка
DDS_GetNode DDS_CalcSinSample DDS_Ctrl DDS_Init DDS_InitOne DDS_Play DDS_Play1kHz DDS_Proc DDS_SetArray DDS_SetFence DDS_SetFramePerSec DDS_SetPattern DDS_SetPwm DDS_SetSaw DDS_SetSin DDS_Stop DDS_GetConfig

Теперь надо проделать то же самое только для h файла.

Фаза 4: Сгенерировать ctags отчет для *.h файла

Сформировать отчет по функциям для *.h файла. Заметьте тут опция другая (—kinds-c=fp).

ctags.exe  --sort=no --kinds-c=fp -fctagsReport.txt dds.h

Фаза 5: Удалить преамбулу

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

sed -i '/!/d' ctagsReport.txt

Фаза 6: Выделить только функции

Выделить из отчёта только функции. Это по сути первая колонка.

gawk '{print $1}' dds.txt > dds_h_functions.txt

Фаза 7: Сравнить последовательности объявления и определения

Так как в файлах dds_h_functions.txt dds_c_functions.txt кристаллизовались фактические последовательности объявлений и определений, то задача свелась к простому сравнению текстовых файлов.

cmp -s dds_h_functions.txt dds_c_functions.txt

Если 0, то файлы одинаковые.

Полный скрипт

Скрипт на CMD выглядит вот так:

set file_h=dds.h set cTagFile=cTag.txt "" > %cTagFile% set FunctionListInC=cFunctions.txt set file_c=dds.c set options=--sort=no set options=%options% -x --c-types=f set options=%options% -w set options=%options% -f%cTagFile% ctags.exe   %options%  %file_c% sed -i '/static/d' %cTagFile% gawk '{print $1}' %cTagFile% > %FunctionListInC% set FunctionListInH=hFunctions.txt set hTagFile=hTag.txt "" > %hTagFile% set options_h=--sort=no set options_h=%options_h% --kinds-c=fp set options_h=%options_h% -f%hTagFile% ctags.exe   %options_h%  %file_h% sed -i '/!/d' %hTagFile% gawk '{print $1}' %hTagFile% > %FunctionListInH% cmp -s %FunctionListInH% %FunctionListInC% echo errorlevel=%errorlevel% if "%errorlevel%"=="0" (echo same) else (echo diff)

Однако скриптовая реализация мне не очень нравится. В скрипт можно залезть ногами и натоптать там так, что он перестанет работать. Поэтому я написал на Си программную смесь чтобы решить конкретно эту задачу. Не больше ни меньше. Я назвал утилиту prototype_check. Утилита просто вызывает консольные команды и печатает лог.

Отладка утилиты

Вот тут утилита prototype_check нашла рассинхрон между последовательностью декларации и определения функций.

Утилита prototype_check сгенерировала файлы nau8814_driver_c_functions.txt и nau8814_driver_h_functions.txt в которых можно увидеть какие именно функции сбились из строя

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

Итоги

Удалось сделать консольную утилиту, которая позволяет проверить, что в *.h файле порядок объявления функций тот же самый, что и порядок определения функций в *.с файле.

Эта утилита позволит автоматически контролировать нарушение вот такого пресловутого требования к оформлению кода.

Заметьте что при разработке tool(ы) были использованы существующие технологии. Утилиты ctags, sed, awk, rm, cmp, cmd и gcc.

Если Вам нужна такая утилита, то пишите. Я пришлю *.exe бинарь.

Ссылки

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Вы делаете одинаковый порядок объявления и определения функций в коде?

50% да5
50% нет5

Проголосовали 10 пользователей. Воздержался 1 пользователь.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

У вас на работе программистом есть требование: «Порядок объявления функций должен совпадать с порядком определения функций.»?

62.5% да5
37.5% нет3

Проголосовали 8 пользователей. Воздержался 1 пользователь.

ссылка на оригинал статьи https://habr.com/ru/articles/844436/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *