CDT(Eclipse JDT的C/C++扩展)在编译不通过的情况下可以生成AST,但生成的AST可能是不完整或包含错误节点的。
CDT使用预处理器和解析器来构建AST:
- 预处理器:处理#include、#define等指令,生成翻译单元。
- 解析器:将预处理后的代码转换为AST节点。
即使代码存在语法错误,CDT的解析器仍会尝试生成尽可能完整的AST,并在错误位置插入特殊的错误节点。
对于编译不通过的代码,CDT会:
- 继续解析后续代码:遇到错误时,解析器会尝试恢复并继续处理后续代码。
- 标记错误位置:在AST中插入错误节点,记录错误类型和位置。
例如,对于以下有语法错误的代码:
int main() {int a = 10;if (a > 5 // 缺少右括号printf("Hello");return 0;
}
CDT可能生成包含错误节点的AST,其中`if`语句的条件表达式节点会被标记为不完整。
下面我们看一下再CDT的Java API中,如何通过获取带有错误的AST:
//创建AST解析器
ASTParser parser = ASTParser.newParser(ICodeAssist.CTTYPE_C);
parser.setSource(code.toCharArray()); // code是包含错误的代码
parser.setProject(project); // 设置项目以解析头文件等
// 获取AST(即使有错误也会返回)
AST ast = parser.createAST(null);
获取AST后,可以通过遍历节点检查是否存在错误:
// 检查AST中的错误
IProblem[] problems = ast.getProblems();
for (IProblem problem : problems) {if (problem.isError()) {System.out.println("错误: " + problem.getMessage());System.out.println("位置: " + problem.getSourceLineNumber());}
}
这种机制在IDE编码过程中错误即时提示和代码分析中非常有用,例如:
- 实时错误提示:IDE可以在用户输入代码时立即显示错误,而不需要等待完整编译。
- 代码静态分析工具:静态分析工具可以基于不完整的AST提取部分信息(如函数调用关系)。
基于CDT生成的AST可以编写代码缺陷检查规则(Checker),这也是静态代码分析工具的核心原理。CDT提供的AST结构包含了代码的语法信息(如函数调用、变量声明、控制流等),通过遍历和分析这些节点,可以识别出潜在的代码缺陷。
CDT AST在代码检查中的应用
CDT的AST节点包含丰富的元数据,例如:
- 节点类型
- 位置信息(行号、列号)
- 关联的符号(如变量、函数名)
- 子节点结构(如表达式的组成部分)
这些信息使得我们可以编写规则来检查:
- 语法层面的缺陷(如未使用的变量、空指针解引用)
- 风格和规范问题(如未遵循命名约定、缺少注释)
- 潜在的逻辑错误(如无限循环、资源泄漏)
以下是一个基于CDT AST的简单Checker示例,用于检测未使用的局部变量:
import org.eclipse.cdt.core.dom.ast.*;
import org.eclipse.cdt.core.dom.ast.cpp.*;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.parser.*;
import org.eclipse.core.runtime.CoreException;
public class UnusedVariableChecker {// 检查给定代码中的未使用局部变量public void checkUnusedVariables(String code, ICProject project) throws CoreException {// 创建AST解析器ASTParser parser = ASTParser.newParser(ICodeAssist.CTTYPE_C);parser.setSource(code.toCharArray());parser.setProject(project);parser.setResolveBindings(true); // 启用符号解析// 生成ASTIASTTranslationUnit tu = parser.createAST(null);// 遍历AST,查找未使用的变量tu.accept(new ASTVisitor() {{// 只关注变量声明节点shouldVisitDeclarations = true;}@Overridepublic int visit(IASTDeclaration declaration) {if (declaration instanceof IASTSimpleDeclaration) {IASTSimpleDeclaration simpleDecl = (IASTSimpleDeclaration) declaration;IASTDeclarator[] declarators = simpleDecl.getDeclarators();for (IASTDeclarator declarator : declarators) {if (declarator.getName() instanceof IASTName) {IASTName name = (IASTName) declarator.getName();// 获取变量的绑定(即符号信息)IBinding binding = name.resolveBinding();if (binding instanceof IVariable) {IVariable variable = (IVariable) binding;// 检查是否为局部变量if (isLocalVariable(variable)) {// 检查变量是否未使用if (!isVariableUsed(variable, tu)) {System.out.println("未使用的局部变量: " +name.getRawSignature() +" (行号: " + name.getFileLocation().getStartingLineNumber() + ")");}}}}}}return PROCESS_CONTINUE;}// 判断是否为局部变量private boolean isLocalVariable(IVariable variable) {if (variable instanceof ICPPVariable) {ICPPVariable cppVar = (ICPPVariable) variable;return cppVar.isLocal();}return false;}// 判断变量是否被使用private boolean isVariableUsed(IVariable variable, IASTTranslationUnit tu) {// 创建引用查找器IASTName[] references = tu.getReferences(variable);return references.length > 1; // 声明本身也算一次引用}});}
}
上面只是一个举例,实际上基于CDT AST可以实现多种Checker,例如:
1. 空指针解引用检查
// 检查空指针解引用if (expr instanceof IASTUnaryExpression) {IASTUnaryExpression unaryExpr = (IASTUnaryExpression) expr;if (unaryExpr.getOperator() == IASTUnaryExpression.op_star) { // *操作符(解引用)IASTExpression operand = unaryExpr.getOperand();// 分析operand是否可能为null}}
2. 资源泄漏检查
// 检查fopen后是否有对应的fcloseif (call.getName().toString().equals("fopen")) {// 记录文件句柄变量} else if (call.getName().toString().equals("fclose")) {// 标记文件句柄已关闭}// 最后检查是否有未关闭的文件句柄
3. 数组越界检查
// 检查数组访问if (expr instanceof IASTArraySubscriptExpression) {IASTArraySubscriptExpression arrayExpr = (IASTArraySubscriptExpression) expr;IASTExpression index = arrayExpr.getArgument();// 分析index是否超出数组边界}
相对于通过ClangTidy等作为引擎开发SAST工具,必须完整编译才能生成AST,进行后续的分析,而利用CDT、JDT具有编译不通过情况下也可以检测分析更具有优势,CDT的AST可以成为强大的代码质量保障工具的底层引擎。
——————————————————————————————————————————
(结束)