CDT&JDT是开发SAST工具的有力引擎

CDT(Eclipse JDT的C/C++扩展)在编译不通过的情况下可以生成AST,但生成的AST可能是不完整或包含错误节点的。

CDT使用预处理器和解析器来构建AST:

  1. 预处理器:处理#include、#define等指令,生成翻译单元。
  2. 解析器:将预处理后的代码转换为AST节点。

即使代码存在语法错误,CDT的解析器仍会尝试生成尽可能完整的AST,并在错误位置插入特殊的错误节点。

对于编译不通过的代码,CDT会:

  1. 继续解析后续代码:遇到错误时,解析器会尝试恢复并继续处理后续代码。
  2. 标记错误位置:在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编码过程中错误即时提示和代码分析中非常有用,例如:

  1. 实时错误提示:IDE可以在用户输入代码时立即显示错误,而不需要等待完整编译。
  2. 代码静态分析工具:静态分析工具可以基于不完整的AST提取部分信息(如函数调用关系)。

基于CDT生成的AST可以编写代码缺陷检查规则(Checker),这也是静态代码分析工具的核心原理。CDT提供的AST结构包含了代码的语法信息(如函数调用、变量声明、控制流等),通过遍历和分析这些节点,可以识别出潜在的代码缺陷。

CDT AST在代码检查中的应用

CDT的AST节点包含丰富的元数据,例如:

  1. 节点类型
  2. 位置信息(行号、列号)
  3. 关联的符号(如变量、函数名)
  4. 子节点结构(如表达式的组成部分)

这些信息使得我们可以编写规则来检查:

  1. 语法层面的缺陷(如未使用的变量、空指针解引用)
  2. 风格和规范问题(如未遵循命名约定、缺少注释)
  3. 潜在的逻辑错误(如无限循环、资源泄漏)

以下是一个基于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); // 启用符号解析

        // 生成AST

        IASTTranslationUnit tu = parser.createAST(null);

       

        // 遍历AST,查找未使用的变量

        tu.accept(new ASTVisitor() {

            {

                // 只关注变量声明节点

                shouldVisitDeclarations = true;

            }

           

            @Override

            public 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后是否有对应的fclose

   if (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可以成为强大的代码质量保障工具的底层引擎。

——————————————————————————————————————————

(结束)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

manok

你的打赏很重要

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值