我们的 DMS 软件重组工具包及其 C++11 前端可用于执行此操作;目前它还没有现成的功能。 DMS 旨在为任意源语言提供自定义工具构建,并包含完整的解析器、名称解析器和各种流分析器来支持分析,以及根据分析结果对代码应用源到源转换的能力。
一般来说,您需要一个静态分析来确定是否使用每个计算(结果,可能有多个,仅考虑“x++”)。对于每个未使用的计算,实际上您希望删除未使用的计算,然后重复分析。出于效率原因,您希望进行一次分析,一次性确定结果的所有(点)使用情况;这本质上是一个数据流分析。当计算结果的使用集变空时,可以删除该计算结果(注意删除“x++”值结果可能会留下“x++”,因为仍然需要增量!)以及它所依赖的计算的使用集可以进行调整以从已删除的引用中删除引用,这可能会导致更多的删除。
要对任何语言进行此分析,您必须能够跟踪结果。对于 C(和 C++)来说,这可能非常难看;在表达式中使用计算结果以及将其分配给本地/全局变量(在其他地方使用)时存在“明显”用途,并且存在通过指针、对象字段更新、通过任意强制转换的间接分配等等。要了解这些影响,您的死代码分析工具必须能够读取entire软件系统,并计算其中的数据流。
为了安全起见,您希望该分析是保守的,例如,如果该工具没有证据表明结果未被使用,那么它必须假设该结果被使用;您通常必须使用指针(或数组索引,它们只是伪装的指针)来执行此操作,因为通常您无法精确确定指针“指向”的位置。显然,可以通过假设使用所有结果来构建一个“安全”工具:-}有时您还会对没有源代码的库例程做出非常保守但必要的假设。在这种情况下,拥有一组库副作用的预先计算的摘要会很有帮助(例如,“strcmp”没有副作用,“sprintf”覆盖特定操作数,“push_back”修改其对象......)。由于库可能非常大,因此这个列表也可能非常大。
DMS 通常可以解析整个源代码库、构建符号表(因此它知道哪些标识符是本地/全局标识符及其精确类型)、进行控制和本地数据流分析、为每个函数构建本地“副作用”摘要、构建调用图形和全局副作用,并进行全局点分析,以适当的保守性提供此“使用的计算”信息。
DMS 已用于在 2600 万行代码的 C 代码系统上执行此计算(是的,这是一个非常大的计算;它需要 100Gb VM 才能运行)。我们没有实现死代码消除部分(该项目有另一个目的),但一旦有了这些数据,这就很简单了。 DMS 通过更保守的分析对大型 Java 代码进行了死代码消除(例如,“没有提及标识符”,这意味着对标识符的赋值是死的),这导致许多实际代码中的代码删除量惊人。
DMS 的 C++ 解析器目前可以构建符号表,并可以对 C++98 进行控制流分析,C++11 也即将推出。我们仍然需要本地数据流分析,这需要一些努力,但全局分析已经存在于 DMS 中,并且可用于实现此效果。 (如果您不介意更保守的分析,可以从符号表数据中轻松获得“不使用标识符”)。
在实践中,您不希望该工具只是默默地把东西撕掉;有些实际上可能是您希望保留的计算。 Java 工具的作用是产生两个结果:死计算列表,您可以检查这些计算以确定您是否相信它,以及源代码的死代码删除版本。如果您相信死代码报告,则保留已删除死代码的版本;如果您看到一个您认为不应该死的“死”计算,您可以修改代码以使其不死,然后再次运行该工具。对于庞大的代码库,检查死代码报告本身可能很困难; “你”如何知道你的团队中的“其他人”是否不重视某些明显无效的代码? (如果你出错了,可以使用版本控制来恢复!)
我们没有(而且我知道没有任何工具)处理的一个非常棘手的问题是条件编译存在时的“死代码”。 (Java 不存在这个问题;C 确实有这个问题,C++ 系统则更少)。这确实很糟糕。想象一个条件,其中一个手臂有某些副作用,而另一个手臂有不同的副作用,或者另一种情况,其中一个手臂由 GCC 的 C++ 编译器解释,而另一个手臂由 MS 解释,并且编译器对构造的作用存在分歧(是的,C++ 编译器在黑暗角落确实不同意)。充其量我们可以在这里非常保守。
CLANG有一定的流量分析能力;以及一些进行源转换的能力,因此它可能被迫这样做。我不知道它是否可以进行任何全局流量/指向分析。它似乎偏向于单个编译单元,因为它的主要用途是编译单个编译单元。