Clang诊断


文档摘要

Clang是一个编译器前端,其最重要的操作与诊断相关。诊断需要精确的源代码位置信息。让我们探索Clang为这些操作提供的基本类。 Clang作为一个编译器,操作文本文件(程序),而定位程序中的特定位置是最常请求的操作。典型的Clang错误报告,Clang为该程序生成的错误消息如下: $ $ lldb \ /llvm-project/install/clang ... (lldb) p sizeof(clang::SourceLocation) (unsigned long) 4 (lldb) 图4.13:在调试器下确定clang::SourceLocation的大小 信息是通过一个无符号长整数编码,这是如何做到的呢?这个数字仅仅作为文本文件中位置的标识符。

Clang是一个编译器前端,其最重要的操作与诊断相关。诊断需要精确的源代码位置信息。让我们探索Clang为这些操作提供的基本类。

Clang作为一个编译器,操作文本文件(程序),而定位程序中的特定位置是最常请求的操作。典型的Clang错误报告,Clang为该程序生成的错误消息如下:

$`<...>/llvm-project/install/bin/clang -fsyntax-only maxerr.cpp
maxerr.cpp:3:12: error: use of undeclared identifier 'ab'
return ab;
^
1 error generated.
\end{shell}

\begin{center}
图4.12:maxerr.cpp中的错误报告
\end{center}

如图4.12所示,显示消息所需的信息包括:

\begin{itemize}
\item
文件名: 在我们的例子中,它是maxerr.cpp

\item
文件行:在我们的例子中,它是3

\item
文件列:在我们的例子中,它是12
\end{itemize}

存储这些信息的结构体应该尽可能紧凑,因为编译器经常使用它。Clang将这些信息存储在clang::SourceLocation对象中。

这个对象经常使用,应该尽可能小且快速复制。可以使用lldb检查对象的大小。如果使用调试器运行Clang,可以确定其大小:

\begin{shell}`$ lldb <...>/llvm-project/install/clang ... (lldb) p
sizeof(clang::SourceLocation) (unsigned long) 4 (lldb)

图4.13:在调试器下确定clang::SourceLocation的大小

信息是通过一个无符号长整数编码,这是如何做到的呢?这个数字仅仅作为文本文件中位置的标识符。另外,需要一个额外的类来正确提取和表示这些信息,这个类是clang::SourceManager,SourceManager对象包含有关特定位置的详细信息。Clang中,管理源位置可能具有挑战性,因为存在宏、包含和其他预处理指令。有几种方式可以解释给定的源位置:

  • 拼写位置:指的是某个东西在源中实际拼写出来的位置。如果有一个指向宏体内部的源位置,拼写位置将给你源代码中宏内容定义的位置。

  • 扩展位置:指的是宏在哪里被扩展。如果有一个指向宏体内部的源位置,扩展位置将给你源代码中使用宏(扩展)的位置。

看一个具体的例子:

#define BAR void bar() int foo(int x); BAR;

图4.14:测试不同类型的源位置的示例程序:functions.hpp

图4.14中,第1行定义了两个函数:int foo()在第2行和void
bar()在第3行。对于第一个函数,拼写位置和扩展位置都指向第2行。对于第二个函数,拼写位置在第1行,而扩展位置在第3行。

用一个测试Clang工具来检查这个例子。将使用第3.4节的测试项目,Recursive
AST
visitor,并在此处替换一些代码部分。首先,必须将clang::ASTContext传递给我们的Visitor实现,因为clang::ASTContext提供对clang::SourceManager的访问。替换图3.8中的第11行,并如下传递ASTContext:

Consumer类(见图3.9)将接受参数并将其作为Visitor的参数使用:

Consumer(clang::ASTContext *Context) :
V(std::make_unique<Visitor>(Context))

主要的修改是在Visitor类中,几乎重写。首先,将clang::ASTContext传递给类构造函数:

图4.15:Visitor类实现:构造函数

AST Context类作为私有成员存储:

private: clang::ASTContext *Context;

图4.16:Visitor类实现:private部分

主要处理逻辑在Visitor::VisitFunctionDecl方法中:

bool VisitFunctionDecl(const clang::FunctionDecl *FD)
clang::SourceManager &SM = Context->getSourceManager();
clang::SourceLocation Loc = FD->getLocation(); clang::SourceLocation
ExpLoc = SM.getExpansionLoc(Loc); clang::SourceLocation SpellLoc =
SM.getSpellingLoc(Loc); llvm::StringRef ExpFileName =
SM.getFilename(ExpLoc); llvm::StringRef SpellFileName =
SM.getFilename(SpellLoc); unsigned SpellLine =
SM.getSpellingLineNumber(SpellLoc); unsigned ExpLine =
SM.getExpansionLineNumber(ExpLoc); llvm::outs() << "Spelling : " <<
FD->getName() << " at " << SpellFileName << ":" << SpellLine
<< ""; llvm::outs() << "Expansion : " << FD->getName() << " at
" << ExpFileName << ":" << ExpLine << ""; return true;

图4.17:Visitor类实现:VisitFunctionDecl方法

如果编译并运行图4.14中的测试文件,输出为:

Spelling : foo at functions.hpp:2 Expansion : foo at functions.hpp:2
Spelling : bar at functions.hpp:1 Expansion : bar at functions.hpp:3

图4.18:recursivevisitor可执行文件在functions.hpp测试文件上的输出

clang::SourceLocation和clang::SourceManager是非常强大的类。与诸如clang::SourceRange(两个源位置的配对,用于指定源范围的开始和结束)等其他类结合使用,为Clang中使用的诊断提供了坚实的基础。

Clang的诊断子系统负责生成和报告警告、错误和其他消息1。涉及的主要类包括:

  • DiagnosticsEngine: 管理诊断ID和选项

  • DiagnosticConsumer: 诊断消费者的抽象基类

  • DiagnosticIDs: 处理诊断标志和内部ID之间的映射

  • DiagnosticInfo: 单个诊断

以下是一个简单的例子,展示了如何在Clang中发出警告:

// Emit a warning
DiagnosticsEngine.Report(DiagnosticsEngine.getCustomDiagID(
clang::DiagnosticsEngine::Warning, "This is a custom warning."));

图4.19:使用clang::DiagnosticsEngine发出警告

我们将使用一个简单的DiagnosticConsumer,即clang::TextDiagnosticPrinter,格式化和打印处理后的诊断消息。

示例中主函数的完整代码如图4.20所示:

int main()

llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagnosticOptions =
new clang::DiagnosticOptions(); clang::TextDiagnosticPrinter
TextDiagnosticPrinter( llvm::errs(), DiagnosticOptions.get(), false);

llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagIDs = new
clang::DiagnosticIDs(); clang::DiagnosticsEngine
DiagnosticsEngine(DiagIDs, DiagnosticOptions, &TextDiagnosticPrinter,
false);

// Emit a warning
DiagnosticsEngine.Report(DiagnosticsEngine.getCustomDiagID(
clang::DiagnosticsEngine::Warning, "This is a custom warning."));

return 0;

图4.20:Clang诊断示例

这段代码将产生以下输出:

warning: This is a custom warning.

图4.21:打印的诊断信息

这个例子中,首先使用TextDiagnosticPrinter作为其DiagnosticConsumer来设置DiagnosticsEngine。然后,使用DiagnosticsEngine的Report方法发出一个自定义警告。我们将在创建第4.6节中的Clang插件的测试项目时添加一个更现实的例子。

  1. LLVM Community. "Clang" CFE Internals Manual. 2023. URL
    https://clang.llvm.org/docs/InternalsManual.html


发布者: 作者: 转发
评论区 (0)
U