我正在为源代码逆向工程平台开发 AST 模型。我的问题是,我应该在我的 AST 中保留括号数据吗?因为我把所有的东西都放在一棵树上,所以我已经知道首先执行哪个操作。
我应该在源建模时保留括号信息吗?
这是一个相当普遍的问题,正确的决定将取决于您希望进行什么样的分析。在逆向工程中,我的理念是信息越多越好。
语法分析
正如 Igor 所说,C 编程语言解析a & 3 == 1
为a & (3 == 1)
,导致代码中存在大量错误。代码没有任何无效的地方,但不太可能是开发人员的意图,并且可以找到语法模式并给出警告。一些编译器已经实现了这一点:
- GCC 不会在其 AST 中存储括号,但会在解析过程中对此发出警告,这表明括号无需在 AST 中即可检测问题(请参阅对
warn_about_parentheses
in的调用gcc/c-typeck.c
)。 - 另一方面,Clang 将括号存储在 AST 中作为一个名为 的节点
ParenExpr
。这允许用于常见的错误条件检查要被从分析器(见分离DiagnoseBitwisePrecedence
中SemaExpr.cpp
)。
阅读了这两种实现后,Clang 设计似乎更可取。该设计可以添加更多模块来分析代码的语法,而无需修改解析器。Clang 还使用空格信息来警告诸如x =+ 1
(而不是x = +1
)之类的事情,并使用代码源信息来防止宏负责过多括号时的警告。尽管在任何非 C 派生语言中都不太可能需要这种确切的功能,但它说明您在 AST 中拥有的信息越多,您可以使用它做的事情就越多。
代码重新生成和调试
正如 jix 所提到的,丢弃有关括号的信息可能不利于从 AST 生成的代码的可读性。如果不存储有关括号的信息,代码重新生成器很可能会x & (y == z)
变成x & y == z
,这既正确又令人困惑。
这也适用于调试 - 您的 AST 对源代码建模得越紧密,您就越容易查看 AST 并了解它与输入代码的对应关系。
语义分析
如您的问题所述,一旦所有内容都解析为一棵树,您就不再需要有关括号的信息来理解代码的语义。如果这是您的唯一意图,那么您的树将在没有括号的情况下变得更小更简单。
反编译器从二进制代码中重建 AST,并对它们进行广泛的分析和转换,而无需了解括号。该语言的运算符优先级用于确定在反编译过程结束时需要将括号放在何处。
执行
如果您出于上述任何原因希望在 AST 中保留括号,我将使用 Clang 的方法并为带括号的表达式添加一个简单的节点。这可能不会向遍历树的任何代码添加超过十行。
jix 上面指出,您可以对分组表达式使用标志。应该注意的是,这不足以表达有效的代码((x + 5))
,并且可能通过让每个节点负责打印自己的括号来鼓励代码重复(尽管我相信您可以解决这个问题)。