我们的 DMS Software Reengineering Toolkit 及其 C++11 前端可用于执行此操作;它目前没有现成的这样做。DMS 旨在为任意源语言提供自定义工具构建,并包含完整的解析器、名称解析器和各种流分析器以支持分析,以及根据分析结果对代码应用源到源转换的能力。
通常,您需要一个静态分析来确定是否使用了每个计算(结果,可能有几个,只考虑“x++”)。对于每个未使用的计算,实际上您想要删除未使用的计算,并重复分析。出于效率原因,您希望进行一次分析,确定结果的所有(点)使用情况;这本质上是一种数据流分析。当计算结果的使用集为空时,可以删除该计算结果(注意删除“x++”值结果可能会留下“x++”,因为仍然需要增量!)以及它所依赖的计算使用集可以调整以从已删除的引用中删除引用,这可能会导致更多删除。
要对任何语言进行此分析,您必须能够跟踪结果。对于 C(和 C++),这可能非常难看。在表达式中使用计算结果以及将其分配给局部/全局变量(在其他地方使用)的“明显”用途,以及通过指针、对象字段更新、任意强制转换的间接分配等。要了解这些影响,您的死代码分析工具必须能够读取整个软件系统,并计算其中的数据流。
为了安全起见,您希望该分析是保守的,例如,如果该工具没有证据证明某个结果未被使用,那么它必须假定该结果已被使用;您通常必须使用指针(或只是伪装的指针的数组索引)来执行此操作,因为通常您无法准确确定指针“指向”的位置。显然,可以通过假设使用所有结果来构建“安全”工具:-} 对于您没有源代码的库例程,您有时也会得到非常保守但必要的假设。在这种情况下,有一组预先计算的库副作用摘要会很有帮助(例如,“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 有一定的流量分析能力;以及一些进行源转换的能力,因此它可能会被迫这样做。我不知道它是否可以进行任何全局流/指向分析。它似乎偏向于单个编译单元,因为它的主要用途是编译单个编译单元。