Как связать измененные строки с функциями в репозитории git кода C?

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

В начале я рассмотрел вывод

git log --patch -M --find-renames --find-copies-harder --function-context -- *.c

Я посмотрел на использование Language.C из Hackage, но, похоже, он хочет, чтобы полная единица перевода, расширенные заголовки и все, а не возможность чтобы справиться с исходным фрагментом.

Опция --function-context новая с версии 1.7.8. Основой реализации в v1.7.9.4 является регулярное выражение:

PATTERNS("cpp",
         /* Jump targets or access declarations */
         "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
         /* C/++ functions/methods at top level */
         "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
         /* compound type at top level */
         "^((struct|class|enum)[^;]*)$",
         /* -- */
         "[a-zA-Z_][a-zA-Z0-9_]*"
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
         "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),

Это, по-видимому, хорошо распознает границы, но doesn & rsquo; t всегда оставляет эту функцию первой линией diff-hunk, например, с директивами #include в верхней части или с помощью hunk, который содержит множество определений функций. Возможность сказать diff для выделения отдельных блоков для каждой измененной функции была бы действительно полезной.

Это не критично, так что я могу терпеть некоторые промахи. Означает ли это, что у меня, вероятно, есть Zawinski & rsquo; s "две проблемы" ?

Ответ 1

Я понимаю, что это предложение немного тангенциально, но оно может помочь в прояснении и ранжировании требований. Это будет работать для C или С++...

Вместо того, чтобы искать текстовые блоки, которые являются функциями и их сравнивать, используйте компилятор для создания двоичных блоков. В частности, для каждого исходного файла C/С++ в наборе изменений скомпилируйте его для объекта. Затем используйте объектный код в качестве основы для сопоставлений.

Это может оказаться для вас нецелесообразным, но у IIRC есть опция для компиляции gcc, чтобы каждая функция была скомпилирована в "независимый фрагмент" в файле сгенерированного объектного кода. Линкер может вытащить каждый "кусок" в программу. (Здесь очень поздно, так что я посмотрю это утром, если вас интересует эта идея.)

Итак, предполагая, что мы можем это сделать, у вас будет множество функций, определенных кусками двоичного кода, поэтому простое сравнение "тепла": "насколько длиннее или короче код между версиями для любой функции?"

Я также думаю, что было бы целесообразно использовать objdump для восстановления ассемблера для функций. Я мог бы использовать некоторые регулярные выражения на этом этапе, чтобы обрезать имена регистров, так что изменения в распределении регистров не вызывают слишком много ложных срабатываний (изменений).

Я бы даже попытался отсортировать команды ассемблера в телах функций и разбить их, чтобы получить шаблон "удаленный" vs "добавлен" между двумя реализациями функций. Это дало бы меру изменений, которая в значительной степени не зависит от макета и даже несколько не зависит от порядка некоторых источников.

Таким образом, было бы интересно увидеть, являются ли две альтернативные реализации одной и той же функции (т.е. из разных наборов изменений) теми же инструкциями: -)

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

Таким образом, регулярные выражения могут быть очень простыми: -)

Предполагая, что все это прямолинейно, что может этот подход не дать вам?

Боковое примечание. Эта базовая стратегия может работать на любом языке, который ориентирован на машинный код, а также наборы команд VM, такие как Java VM Bytecode,.NET CLR-код и т.д.

Ответ 2

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

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

Он может отбрасывать комментарии (сохраняя новые строки), игнорировать содержимое текстовых строк и обрабатывать текст программы очень простым способом. В основном нужно отслеживать сбалансированные "{''} ', сбалансированные' ('') ', а все другие допустимые тексты программы - это только отдельные токены, которые могут передаваться" прямо ".

Этот вывод может быть отдельным файлом/функцией, чтобы упростить отслеживание.

Если язык C или С++, и разработчики разумно дисциплинированы, они никогда не смогут использовать "несинтаксические макросы". Если это так, то файлы не нужно предварительно обрабатывать.

Затем синтаксический анализатор в основном ищет имя функции (идентификатор) в области файла, за которой следует (список параметров) {... code...}

Я бы использовал SWAG для работы на нескольких днях, используя yacc и lex/flex и bison, и это может быть так просто, что их нет необходимости в генераторе синтаксического анализатора.

Если код Java, то ANTLR является возможным, и я думаю, что был простой пример парсера Java.

Если Haskell - ваш фокус, их могут быть опубликованы проекты студентов, которые сделали разумный удар в парсере.