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

Пролог

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

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

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

Понятное дело, что это требование ну никак не влияет на поведение программы во время исполнения. Не делает исполнение кода ни лучше ни хуже. Зато требует дополнительные усилия, ресурсы и время для его исполнения.

f1eaa20be84b6da37a545926e62daa87.png

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

  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

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

4

FC

консольная утилита сравнения файлов

5

cmp

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

6

rm

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

Каков план?

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

f455acb53111e7b357b14bd209db3c98.png

Реализация

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

e5162a1925bde8686a784b7464eb2f08.png

После установки 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 нашла рассинхрон между последовательностью декларации и определения функций.

23df6dae04afaf1128752bd8c0543b7b.png

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

9eb26f5f62dfc86226e2e5e88b2f39ad.png

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

0159d802b3bcd99953fbce0162d64a2b.png

Итоги

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

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

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

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

Ссылки

© Habrahabr.ru