Clang处理错误涉及错误处理。错误处理包括错误检测、显示相应的错误消息,以及可能的错误恢复。后者在Clang AST方面特别引人入胜。当Clang在遇到编译错误时不停止,而是继续编译以检测其他问题,这时就会发生错误恢复。 这种行为之所以有益,有几个原因。最明显的是为了用户方便。当开发者编译程序时,通常希望在单次编译运行中告知尽可能多的错误。如果编译器在第一个错误处停止,程序员将不得不纠正该错误,然后重新编译,接着解决后续的错误,再重新编译,如此往复。这种迭代过程对于较大的代码库,或复杂的错误来说可能会很繁琐和令人沮丧。虽然这种行为对于C/C++等编译型语言特别有用,但值得注意的是,解释型语言也表现出这种行为,这可以帮助用户逐步处理错误。
Clang处理错误涉及错误处理。错误处理包括错误检测、显示相应的错误消息,以及可能的错误恢复。后者在Clang
AST方面特别引人入胜。当Clang在遇到编译错误时不停止,而是继续编译以检测其他问题,这时就会发生错误恢复。
这种行为之所以有益,有几个原因。最明显的是为了用户方便。当开发者编译程序时,通常希望在单次编译运行中告知尽可能多的错误。如果编译器在第一个错误处停止,程序员将不得不纠正该错误,然后重新编译,接着解决后续的错误,再重新编译,如此往复。这种迭代过程对于较大的代码库,或复杂的错误来说可能会很繁琐和令人沮丧。虽然这种行为对于C/C++等编译型语言特别有用,但值得注意的是,解释型语言也表现出这种行为,这可以帮助用户逐步处理错误。
另一个令人信服的原因是IDE集成,IDE提供了导航支持,并集成了编译器。在IDE中编辑代码常常会导致编译错误。大多数错误都局限于代码的特定部分,在这种情况下停止导航可能不是最佳选择。
Clang采用了各种技术来进行错误恢复。在解析的语法阶段,使用启发式方法;例如,如果用户忘记插入分号,Clang可能会尝试在恢复过程中添加它。恢复阶段可以简称为DIRT,其中D代表删除一个字符(例如,多余的分号),I代表插入一个字符(如上面例子所示),R代表替换(替换一个字符以匹配特定的标记),T代表转置(重新排列两个字符以匹配标记)。
如果可能,Clang会执行完全恢复,并生成一个与修改后文件相对应的AST,其中所有编译错误都已修复。最有趣的情况是在无法进行完全恢复时,Clang在创建AST时实施独特的恢复技术。
考虑一个程序(maxerr.cpp),在语法上是正确的,但有一个语义错误。例如,可能使用了未声明的变量。这个程序中,请参考第3行,其中使用了未声明的变量ab:
int max(int a, int b) if (a > b) return ab; return b;
图3.33:带有语义错误的maxerr.cpp测试程序 - 未声明的变量
我们感兴趣的是Clang产生的AST结果,使用clang-query来检查它:
图3.34:编译错误示例
clang-query显示了编译器检测到的编译错误,尽管如此,程序还是生成了一个AST。我们特别感兴趣的是return语句,并可以使用相应的匹配器来突出显示AST的相关部分。
我们还将设置输出以产生AST,并搜索感兴趣的return语句:
clang-query> set output dump clang-query> match returnStmt()
图3.35:设置return语句的匹配器
结果的输出识别了程序中的两个return语句:第一个匹配在第5行,第二个匹配在第3行:
图3.36:maxerr.cpp测试程序中的ReturnStmt节点匹配
第一个匹配对应于第5行语义上正确的代码,并包含对参数a的引用。第二个匹配是第3行的,有一个编译错误。值得注意的是,Clang已经插入了一种特殊的AST节点:RecoveryExpr。某些情况下,Clang可能会产生不完整的AST。这可能会导致Clang工具出现问题,例如lint检查。在编译错误的实例中,lint检查可能会产生意外的结果,因为Clang无法从编译错误中准确恢复。