云题海 - 专业文章范例文档资料分享平台

当前位置:首页 > 使用Clang实现C语言编程规范检查

使用Clang实现C语言编程规范检查

  • 62 次阅读
  • 3 次下载
  • 2025/6/24 22:56:52

概述

Clang是LLVM编译器工具集的前端部分,也就是涵盖词法分析、语法语义分析的部分。而LLVM是Apple在Mac OS上用于替代GCC工具集的编译器软件集合。Clang支持类C语言的语言,例如C、C++、Objective C。Clang的与众不同在于其模块化的设计,使其不仅实现编译器前端部分,并且包装成库的形式提供给上层应用。使用Clang可以做诸如语法高亮、语法检查、编程规范检查方面的工作,当然也可以作为你自己的编译器前端。

编程规范一般包含编码格式和语义规范两部分。编码格式用于约定代码的排版、符号命名等;而语义规范则用于约定诸如类型匹配、表达式复杂度等,例如不允许对常数做逻辑运算、检查变量使用前是否被赋值等。本文描述的主要是基于语义方面的检查,其经验来自于最近做的一个检查工具,该工具实现了超过130条的规范。这份规范部分规则来自于MISRA C 编程模式

编译器前端部分主要是输出代码对应的抽象语法树(AST)。Clang提供给上层的接口也主要是围绕语法树来做操作。通过google一些Clang的资料,你可能会如我当初一样对该如何正确地使用Clang心存疑惑。我最后使用的方式是基于RecursiveASTVisitor。这是一种类似回调的使用机制,通过提供特定语法树节点的接口,Clang在遍历语法树的时候,在遇到该节点时,就会调用到上层代码。不能说这是最好的方式,但起码它可以工作。基于RecursiveASTVisitor使用Clang,程序主体框架大致为:

// 编写你感兴趣的语法树节点访问接口,例如该例子中提供了函数调用语句和goto语句的节点访问接口

classMyASTVisitor: public RecursiveASTVisitor { public:

boolVisitCallExpr(CallExpr*expr);

boolVisitGotoStmt(GotoStmt*stmt); ... };

classMyASTConsumer: public ASTConsumer { public:

virtualboolHandleTopLevelDecl(DeclGroupRef DR) {

for (DeclGroupRef::iterator b =DR.begin(), e =DR.end(); b != e; ++b) { Visitor.TraverseDecl(*b); } returntrue; }

private:

MyASTVisitor Visitor; };

int main(intargc, char**argv) { CompilerInstanceinst; Rewriter writer;

inst.createFileManager();

inst.createSourceManager(inst.getFileManager()); inst.createPreprocessor(); inst.createASTContext();

writer.setSourceMgr(inst.getSourceManager(), inst.getLangOpts()); ... // 其他初始化CompilerInstance的代码

constFileEntry*fileIn=fileMgr.getFile(argv[1]); sourceMgr.createMainFileID(fileIn);

inst.getDiagnosticClient().BeginSourceFile(inst.getLangOpts(), &inst.getPreprocessor());

MyASTConsumerconsumer(writer);

ParseAST(inst.getPreprocessor(), &consumer, inst.getASTContext()); inst.getDiagnosticClient().EndSourceFile(); return0; }

以上代码中,ParseAST为Clang开始分析代码的主入口,其中提供了一个ASTConsumer。每次分析到一个顶层定义时(Top level decl)就会回调

MyASTConsumer::HandleTopLevelDecl,该函数的实现里调用MyASTVisitor开始递归访问该节点。这里的decl实际上包含定义。

这里使用Clang的方式来源于Basic source-to-source transformation with Clang。 语法树

Clang中视所有代码单元为语句(statement),Clang中使用类Stmt来代表statement。Clang构造出来的语法树,其节点类型就是Stmt。针对不同类型的语句,Clang有对应的Stmt子类,例如GotoStmt。Clang中的表达式也被视为语句,Clang使用Expr类来表示表达式,而Expr本身就派生于Stmt。

每个语法树节点都会有一个子节点列表,在Clang中一般可以使用如下语句遍历一个节点的子节点:

for (Stmt::child_iterator it =stmt->child_begin(); it !=stmt->child_end(); ++it) { Stmt*child =*it; }

但遗憾的是,无法从一个语法树节点获取其父节点,这将给我们的规范检测工具的实现带来一些麻烦。

TraverseXXXStmt 在自己实现的Visitor中(例如MyASTVisitor),除了可以提供VisitXXXStmt系列接口去访问某类型的语法树节点外,还可以提供TraverseXXXStmt系列接口。Traverse系列的接口包装对应的Visit接口,即他们的关系大致为:

boolTraverseGotoStmt(GotoStmt*s) { VisitGotoStmt(s); returntrue; }

例如对于GotoStmt节点而言,Clang会先调用TraverseGotoStmt,在TraverseGotoStmt的实现中才会调用VisitGotoStmt。利用Traverse和Visit之间的调用关系,我们可以解决一些因为不能访问某节点父节点而出现的问题。例如,我们需要限制逗号表达式的使用,在任何地方一旦检测到逗号表达式的出现,都给予警告,除非这个逗号表达式出现在for语句中,例如:

a = (a =1, b =2); /* 违反规范,非法 */ for (a =1, b =2; a <2; ++a) /* 合法 */

逗号表达式对应的访问接口为VisitBinComma,所以我们只需要提供该接口的实现即可:

classMyASTVisitor: public RecursiveASTVisitor { public: ...

boolVisitBinComma(BinaryOperator*stmt) { /* 报告错误 */ returntrue; } ... };

(注:BinaryOperator用于表示二目运算表达式,例如a + b,逗号表达式也是二目表达式)

但在循环中出现的逗号表达式也会调用到VisitBinComma。为了有效区分该逗号表达式是否出现在for语句中,我们可以期望获取该逗号表达式的父节点,并检查该父节点是否为for语句。但Clang并没有提供这样的能力,我想很大一部分原因在于臆测语法树(抽象语法树)节点的组织结构(父节点、兄弟节点)本身就不是一个确定的事。 这里的解决办法是通过提供TraverseForStmt,以在进入for语句前得到一个标识:

classMyASTVisitor: public RecursiveASTVisitor { public: ...

// 这个函数的实现可以参考RecursiveASTVisitor的默认实现,我们唯一要做的就是在for语句的头那设定一个标志m_inForLine boolTraverseForStmt(ForStmt*s) { if (!WalkUpFromForStmt(s)) returnfalse;

m_inForLine=true;

for (Stmt::child_range range = s->children(); range; ++range) { if (*range == s->getBody()) m_inForLine=false; TraverseStmt(*range); } returntrue; }

boolVisitBinComma(BinaryOperator*stmt) { if (!m_inForLine) { /* 报告错误 */ } returntrue; } ... };

(注:严格来说,我们必须检查逗号表达式是出现在for语句的头中,而不包括for语句循环体) 类型信息

对于表达式(Expr)而言,都有一个类型信息。Clang直接用于表示类型的类是QualType,实际上这个类只是一个接口包装。这些类型信息可以用于很多类型相关的编程规范检查。例如不允许定义超过2级的指针(例如int ***p):

boolMyASTVisitor::VisitVarDecl(VarDecl*decl) { // 当发现变量定义时该接口被调用

QualType t =decl->getType(); // 取得该变量的类型 intpdepth=0;

// check pointer level

for ( ; t->isPointerType(); t = t->getPointeeType()) { // 如果是指针类型就获取其指向类型(PointeeType) ++pdepth; }

if (pdepth>=3) /* 报告错误 */ }

可以直接调用Expr::getType接口,用于获取指定表达式最终的类型,基于此我们可以检查复杂表达式中的类型转换,例如:

float f =2.0f; double d =1.0;

f = d * f; /* 检查此表达式 */

搜索更多关于: 使用Clang实现C语言编程规范检查 的文档
  • 收藏
  • 违规举报
  • 版权认领
下载文档10.00 元 加入VIP免费下载
推荐下载
本文作者:...

共分享92篇相关文档

文档简介:

概述 Clang是LLVM编译器工具集的前端部分,也就是涵盖词法分析、语法语义分析的部分。而LLVM是Apple在Mac OS上用于替代GCC工具集的编译器软件集合。Clang支持类C语言的语言,例如C、C++、Objective C。Clang的与众不同在于其模块化的设计,使其不仅实现编译器前端部分,并且包装成库的形式提供给上层应用。使用Clang可以做诸如语法高亮、语法检查、编程规范检查方面的工作,当然也可以作为你自己的编译器前端。 编程规范一般包含编码格式和语义规范两部分。编码格式用于约定代码的排版、符号命名等;而语义规范则用于约定诸如类型匹配、表达式复杂度等,例如不允许对常数做逻辑运算、检查变量使用前是否被赋值等。本文描述的主要是基于语义方面的检查,其经验来自于最近做的一个检查工具,该工具实现了超过130条的规范。这份规范部分规则来自于MISRA

× 游客快捷下载通道(下载后可以自由复制和排版)
单篇付费下载
限时特价:10 元/份 原价:20元
VIP包月下载
特价:29 元/月 原价:99元
低至 0.3 元/份 每月下载150
全站内容免费自由复制
VIP包月下载
特价:29 元/月 原价:99元
低至 0.3 元/份 每月下载150
全站内容免费自由复制
注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信:fanwen365 QQ:370150219
Copyright © 云题海 All Rights Reserved. 苏ICP备16052595号-3 网站地图 客服QQ:370150219 邮箱:370150219@qq.com