Стилистический Анализатор: Синхронизация порядка объявлений и определений функций
Пролог
У нас в организации есть правило оформления исходников, которое звучит так:
Порядок объявления функций должен совпадать с порядком определения функций.
В чем проблема?
Понятное дело, что это требование ну никак не влияет на поведение программы во время исполнения. Не делает исполнение кода ни лучше ни хуже. Зато требует дополнительные усилия, ресурсы и время для его исполнения.
Но раз это требование тут существует, то его, как ни крути, приходится выставлять. При этом возникает ряд проблем
У компилятора GCC нет таких ключей, которые бы выявили разный порядок в объявлении и определении функций.
В статическом анализаторе CppCheck нет таких ключей, чтобы выявить разный порядок в объявлении и определении функций
В статическом анализаторе Understand (scitools) нет таких ключей, чтобы выявить разный порядок в объявлении и определении функций
Очевидно также что проблема и в том, что вручную глазами очень утомительно выявлять места нарушения этого странного стерильного правила. Очевидно же, что было бы здорово составить консольную утилиту, которая сама, автоматически покажет и докажет, что этот пресловутый порядок объявления и определения не совпадает.
Поэтому я и решил написать такую утилиту. В этом есть потребность.
Постановка задачи
Написать консольную утилиту, которая будет сообщать программисту о нарушениях соответствия последовательности объявления и определения функций в программах на Си.
Работать с утилитой должно быть просто. Буквально даешь ей *.с файл,
prototype_check.exe cgp dds.c
а утилита сама находит одноименный *.h файл, вычитывает последовательности объявления и вычитывает последовательности определения функций, сравнивает их и сигнализирует об ошибке в виде return кода (0- успех 1 ошибка).
Терминология
Прежде чем двигаться дальше надо кое-что запомнить.
тэг (токен) — это текстовая строка, которая может быть либо названием функции, названием переменной.
СygWin — набор Unix утилит для операционной системы Windows
Что надо из софвера?
Я собираюсь решить эту задачу самым обыкновенным инструментарием из CygWin
№ | Название утилиты | Назначение |
1 | сtags | Создает файл со списком тэгов для данного языка программирования. Эдакий индексатор исходных кодов. |
2 | awk/gawk | программируемый анализатор текстовых строчек |
3 | sed | утилита для авто удаления или авто замены строчек в текстовых файлах |
4 | FC | консольная утилита сравнения файлов |
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 бинарь.
Ссылки