如何将更改后的行与C代码的git存储库中的函数关联起来



我正试图构建一个“热图";来自存储在git存储库中的多年历史,其中粒度单位是单个函数。函数应该随着更改次数的增加、频率的增加以及非空行的更改而变得更热。

首先,我检查了的输出

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

我考虑过使用Hackage的Language.C,但它似乎想要一个完整的翻译单元—展开的标题和所有内容;而是能够处理源片段。

--function-context选项是自1.7.8版本以来的新增选项。v17.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]?"
         "|[-+*/<>%&^|=!]=|--|\+\+|<<=?|>>=?|&&|\|\||::|->"),

这似乎很好地识别了界限,但没有;t总是将函数保留为diff hunk的第一行,例如,顶部有#include指令,或者有一个包含多个函数定义的hunk。告诉diff为每个更改的函数发出单独的chunk的选项将非常有用。

这不是;t安全至关重要,所以我可以容忍一些失误。这是否意味着我可能有Zawinski的;s";两个问题";?

我意识到这个建议有点切中要害,但它可能有助于澄清和排序需求。这适用于C或C++。。。

使用编译器生成二进制块,而不是试图找到作为函数的文本块并进行比较。具体来说,对于变更集中的每个C/C++源文件,将其编译为一个对象。然后使用目标代码作为比较的基础。

这对您来说可能不可行,但IIRC在gcc上有一个编译选项,以便将每个函数编译到生成的对象代码文件中的"独立块"中。链接器可以将每个"chunk"拉入程序中。(现在已经很晚了,所以如果你对这个想法感兴趣,我会在早上查一下。)

所以,假设我们能做到这一点,你会有很多由二进制代码块定义的函数,所以一个简单的"热"比较是"任何函数的版本之间的代码长多少或短多少?"

我还认为,使用objdump来重构函数的汇编程序可能是实用的。在这个阶段,我可能会使用一些正则表达式来修剪寄存器名称,这样对寄存器分配的更改就不会导致太多的误报(更改)。

我甚至可能尝试对函数体中的汇编指令进行排序,并对它们进行比较,以在两个函数实现之间获得"删除"与"添加"的模式。这将给出一个变化的度量,它在很大程度上与布局无关,甚至在某种程度上与某些源的顺序无关。

因此,看看同一功能的两个替代实现(即来自不同的变更集)是否是相同的指令可能会很有趣:-)

这种方法也应该适用于C++,因为所有名称都经过了适当的篡改,这应该可以保证比较相同的函数。

因此,正则表达式可能保持非常简单:-)

假设所有这些都很简单,那么这种方法可能会给你带来什么?

附带说明:这个基本策略可以适用于任何以机器代码为目标的语言,也可以适用于Java VM字节码、.NET CLR代码等VM指令集。

也许值得考虑使用一种常用工具构建一个简单的解析器,而不仅仅是使用正则表达式。显然,最好选择你熟悉的东西,或者你的组织已经使用的东西。

对于这个问题,解析器实际上不需要验证代码(我认为它在签入时是有效的),也不需要理解代码,所以它可能非常愚蠢。

它可能会丢弃注释(保留新行),忽略文本字符串的内容,并以非常简单的方式处理程序文本。它主要需要跟踪平衡的"{"}、平衡的"(")"和所有其他有效的程序文本只是可以"直接"传递的单个令牌。

它的输出可能是一个单独的文件/函数,以便于跟踪。

如果语言是C或C++,并且开发人员有合理的纪律,他们可能永远不会使用"非语法宏"。如果是这样的话,那么这些文件就不需要进行预处理。

然后,解析器主要只是在文件范围内查找函数名(标识符),后跟(参数列表){…code…}

我想用yacc&lex/flex&bison,它可能非常简单,因此不需要解析器生成器。

如果代码是Java,那么ANTLR是可能的,我认为有一个简单的Java解析器示例。

如果Haskell是你关注的焦点,那么他们可能是发布的学生项目,这些项目对解析器进行了合理的尝试。

最新更新