5

我正在重构大量代码,主要是 C++,以删除一些已永久设置为给定值的临时配置检查。因此,例如,我将有以下代码:

#include <value1.h>
#include <value2.h>
#include <value3.h>

...

if ( value1() )
{
    // do something
}

bool b = value2();

if ( b && anotherCondition )
{
    // do more stuff
}

if ( value3() < 10 )
{
    // more stuff again
}

其中对 value 的调用返回 bool 或 int。因为我知道这些调用总是返回的值,所以我做了一些正则表达式替换来将调用扩展到它们的正常值:

// where:
//   value1() == true
//   value2() == false
//   value3() == 4

// TODO: Remove expanded config (value1)
if ( true )
{
    // do something
}

// TODO: Remove expanded config (value2)
bool b = false;

if ( b && anotherCondition )
{
    // do more stuff
}

// TODO: Remove expanded config (value3)
if ( 4 < 10 )
{
    // more stuff again
}

请注意,虽然这些值是固定的,但它们不是在编译时设置的,而是从共享内存中读取的,因此编译器当前没有在幕后优化任何东西。

虽然生成的代码看起来有点傻,但这种正则表达式方法实现了我想要的很多东西,因为它应用简单并且消除了对调用的依赖,同时不会改变代码的行为,而且编译器也可能会优化一个很多事情都知道永远不会调用一个块,否则检查将始终返回 true。它还可以相当容易地(尤其是在针对版本控制进行差异化时)查看发生了什么变化并采取最后的清理步骤,因此上面的代码最终如下所示:

// do something

// DONT do more stuff (b being false always prevented this)

// more stuff again

问题是我要从第二个正确但愚蠢的阶段进行数百次(可能数千次)更改才能获得最终的清理代码。

我想知道是否有人知道可以处理此问题的重构工具或我可以应用的任何技术。主要问题是 C++ 语法使得完全扩展或消除很难实现,并且上面的代码有很多排列。我觉得我几乎需要一个编译器来处理我需要涵盖的语法变化。

我知道有类似的问题,但我找不到任何类似的要求,并且想知道在被问到之后是否出现了任何工具或程序?

4

2 回答 2

5

听起来你有我所说的“僵尸代码”......在实践中已经死了,但就编译器而言仍然存在。对于大多数有组织的运行时配置变量的系统来说,这是一个非常常见的问题:最终,一些配置变量会达到永久固定状态,但会在运行时反复重新评估。

正如您所指出的,解决方法不是正则表达式,因为正则表达式不能可靠地解析 C++ 代码。你需要的是一个程序转换系统。这是一个真正解析源代码的工具,可以将一组代码到代码的重写规则应用到解析树上,并且可以从改变的树中重新生成源文本。

我知道 Clang 在这里有一些能力;它可以解析 C++ 并构建树,但它没有源到源的转换能力。您可以通过编写 AST 到 AST 转换来模拟该功能,但恕我直言,这更不方便。我相信它可以重新生成 C++ 代码,但我不知道它是否会保留注释或预处理器指令。

我们的DMS Software Reengineering Toolkit及其C++(11) 前端可以(并且已用于)对 C++ 源代码进行大量转换,并且具有源到源的转换。AFAIK,它是唯一可以做到这一点的生产工具。您需要的是一组转换,这些转换代表您对感兴趣的配置变量的最终状态的了解,以及一些简单的代码简化规则。以下 DMS 规则与您可能想要的很接近:

  rule fix_value1():expression->expression
    "value1()" -> "true";
  rule fix_value2():expression->expression
    "value2()" -> "false";
  rule fix_value3():expression->expression
    "value3()" -> "4";

  rule simplify_boolean_and_true(r:relation):condition->condition
     "r && true" -> "r".
  rule simplify_boolean_or_ture(r:relation):condition->condition
     "r || true" -> "true".
  rule simplify_boolean_and_false(r:relation):condition->condition
     "r && false" -> "false".
  ...
  rule simplify_boolean_not_true(r:relation):condition->condition
     "!true" -> "false".
  ...

  rule simplify_if_then_false(s:statement): statement->statement
      " if (false) \s" -> ";";
  rule simplify_if_then_true(s:statement): statement->statement
      " if (true) \s" -> "\s";
  rule simplify_if_then_else_false(s1:statement, s2:statement): statement->statement
      " if (false) \s1 else \s2" -> "\s2";
  rule simplify_if_then_else_true(s1:statement, s2: statement): statement->statement
      " if (true) \s1 else \s2" -> "\s2";

您还需要规则来简化(“折叠”)涉及算术的常量表达式,以及处理打开现在为常量的表达式的规则。要查看整数常量折叠的 DMS 规则是什么样的,请参阅作为 DMS 域的代数

与正则表达式不同,DMS 重写规则不能“不匹配”代码;它们代表相应的 AST,并且匹配的是 AST。因为它是 AST 匹配,所以它们没有空格、换行或注释的问题。您可能认为他们可能在操作数的顺序上遇到问题('如果遇到“false && x”怎么办?');他们没有,因为&&||的语法规则 在 DMS C++ 解析器中被标记为关联和交换,并且匹配过程会自动将其考虑在内。

这些规则本身不能做的是跨赋值的值(在你的情况下是常量)传播。为此,您需要进行流分析,以便您可以跟踪此类分配(“到达定义”)。显然,如果您没有这样的任务或很少,您可以手动修补这些任务。如果这样做,您将需要流量分析;唉,DMS 的 C++ 前端并不完全在那里,但我们正在努力;我们有控制流分析。(DMS的C前端有全流分析)。

(编辑 2015 年 2 月:现在执行完整的 C++14;函数/方法内的流分析)。

实际上,我们在大约十年前将这项技术应用于 IBM Tivoli 的 1.5M SLOC 混合 C 和 C++ 代码应用程序,并取得了巨大成功;我们不需要流量分析:-}

于 2012-05-02T10:07:49.907 回答
1

你说:

请注意,尽管这些值是合理固定的,但它们不是在编译时设置的,而是从共享内存中读取的,因此编译器当前没有在幕后优化任何东西。

除非它们完全固定,否则手动不断折叠值没有多大意义。如果您的编译器提供constexpr您可以使用它,或者您可以像这样替换预处理器宏:

#define value1() true
#define value2() false
#define value3() 4

优化器会从那里照顾你。在没有看到<valueX.h>标题中确切内容的示例或不知道从共享内存中获取这些值的过程是如何工作的情况下,我只会抛出重命名现有 valueX() 函数并进行运行时检查以防万一他们在未来再次改变:

// call this at startup to make sure our agreed on values haven't changed
void check_values() {
    assert(value1() == get_value1_from_shared_memory());
    assert(value2() == get_value2_from_shared_memory());
    assert(value3() == get_value3_from_shared_memory());
}
于 2012-04-11T09:26:18.707 回答