Clang比传统编译器具有更加友好的错误提示。

本文主要对Diagnostics模块中的相关API做一个简单梳理。

DiagnosticsEngine

我们主要用DiagnosticsEngine类中的Report()方法报告诊断信息。该方法的函数声明如下:

DiagnosticBuilder Report (SourceLocation Loc, unsigned DiagID)
DiagnosticBuilder Report (unsigned DiagID)

SourceLocation记录了源代码中对应的位置信息。

每个诊断都需要指定一个唯一的DiagID. DiagID可以通过DiagnosticsEngine类中的getCustomDiagID()方法生成,其函数声明如下:

unsigned getCustomDiagID (Level L, const char(&FormatString)[N])

其中,

Level是指定错误级别:

enum clang::DiagnosticsEngine::Level{
  Ignored = DiagnosticIDs::Ignored,
  Note = DiagnosticIDs::Note,
  Remark = DiagnosticIDs::Remark,
  Warning = DiagnosticIDs::Warning,
  Error = DiagnosticIDs::Error,
  Fatal = DiagnosticIDs::Fatal
}

const char(&FormatString)[N]是一段格式化的错误文字描述。具体用法可以参见The Format String.

例子

对以下C代码,我们想实现一个Checker找出其中if语句可能存在的问题。

void foo(int id){
    if(id = 1){
        // ...
    }
}

根据上篇文章中的方法,我们首先编写相应的AST Matcher匹配到该if语句:

StatementMatcher Matcher = ifStmt(hasCondition(ignoringImpCasts(
  binaryOperator(hasOperatorName("=")).bind("if_cond_assign"))));

MatchCallback中的run()方法为:

virtual void run(const MatchFinder::MatchResult &Result) {
  if(const BinaryOperator *Op = Result.Nodes.getNodeAs<BinaryOperator>("if_cond_assign")){
    ASTContext *Context = Result.Context;
    DiagnosticsEngine &DE = Context->getDiagnostics();
    const auto DiagID = DE.getCustomDiagID(clang::DiagnosticsEngine::Warning,
                                   "Using the result of an assignment as a condition. Do you mean '%0'?");
    DE.Report(Op->getOperatorLoc(), DiagID).AddString("==");
  }
}

DiagnosticsEngine类可以从ASTContext中获取。

运行结果:

test.cpp:2:11: warning: Using the result of an assignment as a condition. Do you mean '=='?
    if(id = 1){
          ^
1 warning generated.

DiagnosticBuilder

DiagnosticsEngine.Report()方法会返回一个DiagnosticBuilder类。这里我们主要介绍其中常用的几个函数。

  • void AddString (StringRef S) const

上面例子我们也用到了该方法,它用于替换FormatString中的%0, %1

  • void AddFixHint (const FixItHint &Hint) const

该方法用于添加FixItHint,在下节介绍。

  • void AddSourceRange (const CharSourceRange &R) const

Source Range

获取CharSourceRange的方式:(具体见doxygen)

  • static clang::CharSourceRange::getCharRange()

  • static clang::CharSourceRange::getTokenRange()

修改上述例子中的代码,

virtual void run(const MatchFinder::MatchResult &Result) {
  if(const BinaryOperator *Op = Result.Nodes.getNodeAs<BinaryOperator>("if_cond_assign")){
    ASTContext *Context = Result.Context;
    DiagnosticsEngine &DE = Context->getDiagnostics();
    const auto DiagID = DE.getCustomDiagID(clang::DiagnosticsEngine::Warning,
                                   "Using the result of an assignment as a condition. Do you mean '%0'?");
    DiagnosticBuilder DB = DE.Report(Op->getOperatorLoc(), DiagID);
    DB.AddString("==");
    const auto Range = clang::CharSourceRange::getCharRange(Op->getSourceRange());
    DB.AddSourceRange(Range);
  }
}

运行结果:

test.cpp:2:11: warning: Using the result of an assignment as a condition. Do you mean '=='?
    if(id = 1){
       ~~~^~
1 warning generated.

Expr类用getCharRange()方法并不能显示出SouceRange,需要用getTokenRange()

例如:

clang::CharSourceRange::getTokenRange(EXP->getLocStart(), EXP->getLocEnd());

运行结果:

    switch(flag){
    ^      ~~~~

Fix-It Hints

FixItHint类有三种创建方式:

  • static clang::FixItHint::CreateInsertion(Loc, Code)
  • static clang::FixItHint::CreateReplacement(Range, Code)
  • static clang::FixItHint::CreateRemoval(Range)

下面以替换为例:

virtual void run(const MatchFinder::MatchResult &Result) {
  if(const BinaryOperator *Op = Result.Nodes.getNodeAs<BinaryOperator>("if_cond_assign")){
    ASTContext *Context = Result.Context;
    DiagnosticsEngine &DE = Context->getDiagnostics();
    const auto DiagID = DE.getCustomDiagID(clang::DiagnosticsEngine::Warning,
                                   "Using the result of an assignment as a condition.");
    DiagnosticBuilder DB = DE.Report(Op->getOperatorLoc(), DiagID);
    const SourceLocation Start = Op->getOperatorLoc();
    const SourceLocation End = Start.getLocWithOffset(+1);
    const auto Range = clang::CharSourceRange::getCharRange(Start, End);
    const auto FixIt = clang::FixItHint::CreateReplacement(Range, "==");
    DB.AddFixItHint(FixIt);
  }
}

运行结果:

test.cpp:2:11: warning: Using the result of an assignment as a condition.
    if(id = 1){
          ^
          ==
1 warning generated.


参考