2401llvm,合并clang语法树

ASTImporter:合并ClangAST

ASTImporter类是Clang的核心库AST库的一部分.它导入一个ASTContext的节点到另一个ASTContext中.
这里,假设你对ClangAST有基本了解.如果你想了解有关AST结构的更多信息,见ClangAST简介.匹配ClangAST在此.

介绍

ASTContext包含长期有的,可在文件的整个分析语义过程中引用的AST节点(如类型声明).有时,最好使用多个ASTContext.

如,想在同一个Clang工具中,解析多个不同的文件.如果可像解析每个文件产生一个AST一样的,查看生成AST集,就会很方便.

ASTImporter提供了可从一个ASTContext复制声明类型到另一个ASTContext的方法.从中导入环境"从"环境或源环境;

"到"环境或目标环境为导入进的环境.

ASTImporter库的现有用户,是交叉翻译单元(CTU)静态分析和LLDB式解析器.如果在另一个,(TU)翻译单元中找到函数定义,则CTU静态分析导入函数定义.

这样,分析就可突破单个TU限制.LLDB命令解析用户定义的式,为其创建一个ASTContext,然后从从调试信息(DWARF等)的AST导入中获得缺失定义.

导入算法

导入一个AST节点,会复制该节点到目标ASTContext中.为什么必须复制节点,而不能插入该节点指针到目标环境中呢?一个原因是"from"环境可能比"to"环境更长久.

此外,如果节点有相同地址,ClangAST会认为节点(或节点的某些属性)是等效的!

导入算法必须确保不同翻译单元中,结构等效的节点,不会在合并的AST中重复.如,如果在两个翻译单元中,包含向量模板(#include<vector>)的定义,则合并的AST应该只有一个代表模板节点.

此外,必须发现(ODR)一个定义规则的违规行为.如,如果两个翻译单元中,有相同名字的类定义,但其中一个定义包含不同数量的字段.

因此,要查找现有定义,然后检查这些节点上的结构等价性.以下伪代码演示了导入机制的底层:

//导入的伪代码(!):
ErrorOrDecl Import(Decl *FromD) {Decl *ToDecl = nullptr;Found声明List = 用FromD相同名,在`to`环境中查找所有声明for (auto FoundDecl : Found声明List) {if (StructurallyEquivalent声明(FoundDecl, FromD)) {ToDecl = FoundDecl;Mark FromD as imported;break;} else {Report ODR violation;return error;}}if (Found声明List is empty) {导入依赖声明及to声明的类型ToDecl = 在`to`环境创建新AST;Mark FromD as imported;}return ToDecl;
}

如果两个AST节点在结构上是等效的,则它们是等效的.
1,内置类型和引用相同类型,如intint结构上是等价的,
2,函数类型及其所有参数在结构上有等效类型,
3,记录类型及其所有字段(按其定义顺序)有相同的标识名结构上等效的类型,
4,变量或函数声明,且有相同标识名,且它们的类型结构上是等效的.

可把定义结构等价类似地扩展到模板.

应用接口

创建一个使用ASTImporter类的工具!首先,从虚文件构建两个AST;虚文件的内容是从串字面合成的:

std::unique_ptr<ASTUnit> ToUnit = buildASTFromCode("", "to.cc"); //空文件
std::unique_ptr<ASTUnit> FromUnit = buildASTFromCode(R"(class MyClass {int m1;int m2;};)","from.cc");

第一个AST对应("to")为空的目标环境,第二个AST对应源("from")环境.接着,定义一个匹配"from"环境中的MyClass的匹配器:

auto Matcher = cxxRecordDecl(hasName("MyClass"));
auto *From = getFirstDecl<CXXRecordDecl>(Matcher, FromUnit);

现在创建导入器并导入:

ASTImporter Importer(ToUnit->getASTContext(), ToUnit->getFileManager(), FromUnit->getASTContext(), FromUnit->getFileManager(), /*`MinimalImport=`*/true);
llvm::Expected<Decl *> ImportedOrErr = Importer.Import(From);

Import调用返回llvm::Expected,因此,必须检查是否有错误.细节,见错误处理文档.

if (!ImportedOrErr) {llvm::Error Err = ImportedOrErr.takeError();llvm::errs() << "ERROR: " << Err << "\n";consumeError(std::move(Err));return 1;
}

如果正确,则可得到底层值.此例中,打印"to"环境的AST.

Decl *Imported = *ImportedOrErr;
Imported->getTranslationUnitDecl()->dump();

因为在导入器的构造器中,设置了最小导入,因此(一旦运行测试工具)AST不包含成员声明.
要想得到成员,所以,用ImportDefinition复制MyClass的整个定义到"to"环境中.然后再次转储AST.

if (llvm::Error Err = Importer.ImportDefinition(From)) {llvm::errs() << "ERROR: " << Err << "\n";consumeError(std::move(Err));return 1;
}
llvm::errs() << "Imported definition.\n";
Imported->getTranslationUnitDecl()->dump();

这一次,AST也包含成员了.
如果把导入器设置为执行"正常"(非最小)导入,则可省去调用ImportDefinition.

ASTImporter Importer( ....  /*`MinimalImport=`*/false);

正常导入时,会正常导入所有依赖声明.但是,在最小导入下,会不带定义的导入依赖声明,如果稍后需要,必须为每个声明导入它们的定义.

放在一起:

#include "clang/AST/ASTImporter.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
using namespace clang;
using namespace tooling;
using namespace ast_matchers;
template <typename Node, typename Matcher>
Node *getFirstDecl(Matcher M, const std::unique_ptr<ASTUnit> &Unit) {auto MB = M.bind("bindStr"); //把`待匹配节点`绑定到`串键`.auto MatchRes = match(MB, Unit->getASTContext());//至少应该有一个匹配.assert(MatchRes.size() >= 1);//取第一个`匹配`及绑定节点.Node *Result =const_cast<Node *>(MatchRes[0].template getNodeAs<Node>("bindStr"));assert(Result);return Result;
}
int main() {std::unique_ptr<ASTUnit> ToUnit = buildASTFromCode("", "to.cc");std::unique_ptr<ASTUnit> FromUnit = buildASTFromCode(R"(class MyClass {int m1;int m2;};)","from.cc");auto Matcher = cxxRecordDecl(hasName("MyClass"));auto *From = getFirstDecl<CXXRecordDecl>(Matcher, FromUnit);ASTImporter Importer(ToUnit->getASTContext(), ToUnit->getFileManager(), FromUnit->getASTContext(), FromUnit->getFileManager(), /*`MinimalImport=`*/true);llvm::Expected<Decl *> ImportedOrErr = Importer.Import(From);if (!ImportedOrErr) {llvm::Error Err = ImportedOrErr.takeError();llvm::errs() << "ERROR: " << Err << "\n";consumeError(std::move(Err));return 1;}Decl *Imported = *ImportedOrErr;Imported->getTranslationUnitDecl()->dump();if (llvm::Error Err = Importer.ImportDefinition(From)) {llvm::errs() << "ERROR: " << Err << "\n";consumeError(std::move(Err));return 1;}llvm::errs() << "Imported definition.\n";Imported->getTranslationUnitDecl()->dump();return 0;
};

假定clang/tools构建和链接说明,在此扩展CMakeLists.txt:

add_clang_executable(astimporter-demo ASTImporterDemo.cpp)
clang_target_link_libraries(astimporter-demoPRIVATELLVMSupportclangASTclangASTMatchersclangBasicclangFrontendclangSerializationclangTooling)

然后,可构建并执行新工具.

$ ninja astimporter-demo && ./bin/astimporter-demo

导入过程中的错误

一般,源或目标环境都包含声明定义.但是,有时,两个环境都定义了给定符号.如果这些定义不同,则就有名字冲突,在C++中,叫做ODR(一个定义规则)违规.

修改之前编写的工具,并用冲突定义试导入ClassTemplateSpecializationDecl:

int main() {std::unique_ptr<ASTUnit> ToUnit = buildASTFromCode(R"(//主模板template <typename T>struct X {};//显式特化template<>struct X<int> { int i; };)","to.cc");ToUnit->enableSourceFileDiagnostics();std::unique_ptr<ASTUnit> FromUnit = buildASTFromCode(R"(//主模板template <typename T>struct X {};//显式特化template<>struct X<int> { int i2; };//字段不匹配:^^)","from.cc");FromUnit->enableSourceFileDiagnostics();auto Matcher = classTemplateSpecializationDecl(hasName("X"));auto *From = getFirstDecl<ClassTemplateSpecializationDecl>(Matcher, FromUnit);auto *To = getFirstDecl<ClassTemplateSpecializationDecl>(Matcher, ToUnit);ASTImporter Importer(ToUnit->getASTContext(), ToUnit->getFileManager(), FromUnit->getASTContext(), FromUnit->getFileManager(), /*`MinimalImport=`*/false);llvm::Expected<Decl *> ImportedOrErr = Importer.Import(From);if (!ImportedOrErr) {llvm::Error Err = ImportedOrErr.takeError();llvm::errs() << "ERROR: " << Err << "\n";consumeError(std::move(Err));To->getTranslationUnitDecl()->dump();return 1;}return 0;
};

运行该工具时,会收到以下警告:

 `to.cc:7:14:`警告:`"X<int>"`类型在不同的翻译单元中有不兼容的定义`[-Wodr]``构X<int>{inti;`^`to.cc:7:27:`注意:此处的`字段名`叫`"i"``构X<int>{inti;`^`from.cc:7:27:`注意:此处的`字段名`叫`"i2"``structX<int>{inti2;`^

注意,因为这些诊断,必须在ASTUnit对象上调用enableSourceFileDiagnostics.
因为无法导入指定的(From)声明,因此返回值中出现错误.AST不包含冲突定义,因此只剩下原始AST.

错误传播

如果在导入给定节点前,有必须先导入的依赖节点,则把与依赖关系关联的导入错误传播到依赖节点.修改前例并导入FieldDecl而不是ClassTemplateSpecializationDecl.

auto Matcher = fieldDecl(hasName("i2"));
auto *From = getFirstDecl<FieldDecl>(Matcher, FromUnit);

本例中,可见(getImportDeclErrorIfAny)错误不仅是字段,也与特化相关联:

llvm::Expected<Decl *> ImportedOrErr = Importer.Import(From);
if (!ImportedOrErr) {llvm::Error Err = ImportedOrErr.takeError();consumeError(std::move(Err));//检查是否也按错误标记`ClassTemplateSpecializationDecl`.auto *FromSpec = getFirstDecl<ClassTemplateSpecializationDecl>(classTemplateSpecializationDecl(hasName("X")), FromUnit);assert(Importer.getImportDeclErrorIfAny(FromSpec));//顺便,也为`FieldDecl`设置错误.assert(Importer.getImportDeclErrorIfAny(From));return 1;
}

污染的AST

可能会在导入依赖节点时,发现错误.但是,那时,已创建了依赖项.这时,不会从"to"环境中删除现有的错误节点,而是关联一个错误该节点.

用另一个Y类来扩展前例.此类"to"环境中有前向定义,但在"from"环境中定义它.要想导入定义,但它包含一个类型与"to"环境中的类型冲突的成员:

std::unique_ptr<ASTUnit> ToUnit = buildASTFromCode(R"(//主模板template <typename T>struct X {};//显式特化template<>struct X<int> { int i; };class Y;)","to.cc");
ToUnit->enableSourceFileDiagnostics();
std::unique_ptr<ASTUnit> FromUnit = buildASTFromCode(R"(//主模板template <typename T>struct X {};//显式特化template<>struct X<int> { int i2; };//字段不匹配:^^class Y { void f() { X<int> xi; } };)","from.cc");
FromUnit->enableSourceFileDiagnostics();
auto Matcher = cxxRecordDecl(hasName("Y"));
auto *From = getFirstDecl<CXXRecordDecl>(Matcher, FromUnit);
auto *To = getFirstDecl<CXXRecordDecl>(Matcher, ToUnit);

这一次,为ASTImporterSharedState创建一个拥有"to"环境关联错误shared_ptr.注意,可能会有几个不同的ASTImporter对象,从不同的"from"环境导入,但导入到相同的"to"环境中;

它们应共享相同的ASTImporterSharedState.注意,必须包含相应的ASTImporterSharedState.h头文件.

auto ImporterState = std::make_shared<ASTImporterSharedState>();
ASTImporter Importer(ToUnit->getASTContext(), ToUnit->getFileManager(), FromUnit->getASTContext(), FromUnit->getFileManager(), /*`MinimalImport=`*/false, ImporterState);
llvm::Expected<Decl *> ImportedOrErr = Importer.Import(From);
if (!ImportedOrErr) {llvm::Error Err = ImportedOrErr.takeError();consumeError(std::move(Err));//...但是已创建`节点`.auto *ToYDef = getFirstDecl<CXXRecordDecl>(cxxRecordDecl(hasName("Y"), isDefinition()), ToUnit);ToYDef->dump();//在共享状态下,已为`"ToYDef"`设置了错误.Optional<ASTImportError> OptErr =ImporterState->getImportDeclErrorIfAny(ToYDef);assert(OptErr);return 1;
}

如果看一下AST,则可见创建了带定义的Decl,但缺少字段.

不会删除错误节点,因为当错误识别时,再删除节点为时已晚,可能会有对AST中已有节点其他引用.
这与ClangAST的整体设计原则一致:ClangAST节点(类型,声明,语句,式等)一般按创建后不变设计.

因此,ASTImporter库的用户,应总是在目标环境中,检查待检查节点是否有相关错误.建议跳过有关联错误节点的处理.

使用-ast-mergeClang前端操作

-ast-merge<pch-file>命令行开关,可用来从给定的表示源环境的序化AST文件合并.有此开关时,会把源环境的每个顶级AST节点都合并目标环境中.
如果合并成功,则为声明调用ASTConsumer::HandleTopLevelDecl.这导致可在扩展的AST上执行原始前端操作.

C示例

考虑以下三个文件:

//bar.h
#ifndef BAR_H
#define BAR_H
int bar();
#endif /*`BAR_H`*/
//`bar.c`
#include "bar.h"
int bar() {return 41;
}
//`main.c`
#include "bar.h"
int main() {return bar();
}

为两个源文件生成AST文件:

$ clang -cc1 -emit-pch -o bar.ast bar.c
$ clang -cc1 -emit-pch -o main.ast main.c

然后,如果只考虑bar()函数,检查一下合并AST会怎样:

$ clang -cc1 -ast-merge bar.ast -ast-merge main.ast /dev/null -ast-dump

可检查函数的原型和它的定义是否合并到同一个再声明链中.更重要的是,还合并了第三个原型声明到中.
函数的合并方式是,如果原型引用相同类型,则添加原型再声明链中,但只能有一个定义.

两个声明bar.ast,第三个声明是main.ast.
现在,从合并的AST创建一个目标文件:

$ clang -cc1 -ast-merge bar.ast -ast-merge main.ast /dev/null -emit-obj -o main.o
Next, we may call the linker and execute the created binary file.
$ clang -o a.out main.o
$ ./a.out
$ echo $ 
41
$

C++示例

C++时,生成AST文件及调用前端方式有点不同.假设有这三个文件:

//`foo.h`
#ifndef FOO_H
#define FOO_H
struct foo {virtual int fun();
};
#endif /*`FOO_H`*/
//`foo.cpp`
#include "foo.h"
int foo::fun() {return 42;
}
//`main.cpp`
#include "foo.h"
int main() {return foo().fun();
}

生成AST文件,合并它们,创建可执行文件,然后运行它:

$ clang++ -x c++-header -o foo.ast foo.cpp
$ clang++ -x c++-header -o main.ast main.cpp
$ clang++ -cc1 -x c++ -ast-merge foo.ast -ast-merge main.ast /dev/null -ast-dump
$ clang++ -cc1 -x c++ -ast-merge foo.ast -ast-merge main.ast /dev/null -emit-obj -o main.o
$ clang++ -o a.out main.o
$ ./a.out
$ echo $ 
42
$

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/650180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新建一个基于标准库的工程(STM32)

目录 1.新建存放工程的文件夹 2.打开KEIL5软件 3.新建一个本次工程的文件夹 4.添加工程的必要文件 4.1打开STM32的启动文件 ​编辑 4.2&#xff1a; 4.3添加内核寄存器文件 ​编辑 5.回到keil5软件&#xff0c;将刚才复制的那些文件添加到工程中 5.1添加一个启动文件&am…

浅谈WPF之样式与资源

WPF通过样式&#xff0c;不仅可以方便的设置控件元素的展示方式&#xff0c;给用户呈现多样化的体验&#xff0c;还简化配置&#xff0c;避免重复设置元素的属性&#xff0c;以达到节约成本&#xff0c;提高工作效率的目的&#xff0c;样式也是资源的一种表现形式。本文以一个简…

k8s-基础知识(Service,NodePort,CusterIP,无头服务,NameSpace,资源限制)

Node Node 是 Pod 真正运行的主机&#xff0c;可以是物理机&#xff0c;也可以是虚拟机。 Annotations 原文链接 Annotations 是 key/value 形式附加于对象的注解。不同于 Labels 用于标志和选择对象&#xff0c;Annotations 则是用来记录一些附加信息&#xff0c;用来辅助应…

x-cmd pkg | httpx - 为 Python 设计的下一代 HTTP 客户端库

目录 简介首次用户功能特点进一步探索 简介 HTTPX 是一个为 Python 设计的下一代 HTTP 客户端库&#xff0c;由 Tom Christie 创建。它提供了同步和异步的 API&#xff0c;并支持 HTTP/1.1 和 HTTP/2 协议。与 Requests 库类似&#xff0c;但增加了对异步请求的支持和 HTTP/2 …

MySql8的简单使用(1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践)

MySql8的简单使用&#xff08;1.模糊查询 2.group by 分组 having过滤 3.JSON字段的实践&#xff09; 一.like模糊查询、group by 分组 having 过滤 建表语句 create table student(id int PRIMARY KEY,name char(10),age int,sex char(5)); alter table student add height…

HTML 高级进阶试题——附答案

选择题 问题&#xff1a; HTML 中的 <article> 元素的主要目的是什么&#xff1f; A. 表示主要内容B. 定义页面的主体部分C. 包含一篇文章 问题&#xff1a; 在 HTML 中&#xff0c;data-* 属性的主要作用是什么&#xff1f; A. 存储元素的样式信息B. 存储元素的自定义数…

【TOP解刊】IEEE(trans)顶刊,国人绝对优势,同领域2个月录用,5天见刊!

工程技术类 • 顶刊解读 今天带来IEEE旗下工程技术领域顶刊&#xff0c;究竟这本高分期刊审稿情况如何呢&#xff1f;好投吗&#xff1f;一起来看看下文解析。如有投稿意向可重点关注&#xff0c;具体详情见下文&#xff1a; 01 期刊简介 IEEE Transactions on Power Electr…

FlashInternImage实战:使用 FlashInternImage实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

中间件与rabbitmq

中间件是一种软件&#xff0c;用于在不同的应用程序、系统或服务之间提供通用功能和服务。它充当应用程序之间的桥梁&#xff0c;帮助它们相互通信和交换数据。中间件简化了复杂软件系统的开发和维护&#xff0c;使不同的系统组件能够更容易地协同工作。中间件的类型很多&#…

QDockWidget : 想要 top -> left -> rigt -> bottom 的布局实现

上图红圈中的实现&#xff0c;第一次想要实现&#xff0c;总会和想的不一样。 第一种情况 第二种情况 第三种情况 有时候为了达到一种效果&#xff0c;也算是煞费苦心了&#xff0c;且不说这个demo还是找的其他CSDN博主的&#xff0c;但是功夫不负有心人。 解决办法 先让 Doc…

[UI5 常用控件] 03.Icon, Avatar,Image

文章目录 前言1. Icon2. Avatar2.1 displayShape2.2 initials2.3 backgroundColor2.4 Size2.5 fallbackIcon2.6 badgeIcon2.7 badgeValueState2.8 active 3. Image 前言 本章节记录常用控件Title,Link,Label。 其路径分别是&#xff1a; sap.m.Iconsap.m.Avatarsap.m.Image 1…

01_ESP32 MicroPython开发环境搭建

一、工作原理 Python源代码->Python解释器(MicroPython)-->二进制代码(01010)-->硬件电路(ESP32)-->高低电平输出-->其他设备 二、准备工作&#xff1a; 硬件&#xff1a;ESP32开发版&#xff0c;有很多个版本可选&#xff0c;我这里用的是ESP-32开发板&…

K8s 安装部署-Master和Minion(Node)文档

K8s 安装部署-Master和Minion(Node)文档 操作系统版本&#xff1a;CentOS 7.4 Master &#xff1a;172.20.26.167 Minion-1&#xff1a;172.20.26.198 Minion-2&#xff1a;172.20.26.210&#xff08;后增加节点&#xff09; ETCD&#xff1a;172.20.27.218 先安装部署ETC…

navicat 可以直接往 mysql导入excel表格

妈呀 还好 提前问了一下&#xff0c;不然哼哧哼哧在那里写&#xff0c;导入接口。。

05-TiDB 之 HTAP 快速上手

混合型在线事务与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP) 功能 HTAP 存储引擎&#xff1a;行存 与列存 同时存在&#xff0c;自动同步&#xff0c;保持强一致性。行存 OLTP &#xff0c;列存 OLAPHTAP 数据一致性&#xff1a;作为一个分布式事务…

【王道数据结构】【chapter2线性表】【P43t15】

单链表有环&#xff0c;是指单链表的最后一个节点的指针指向了链表中的某个结点&#xff08;通常单链表的最后一个节点的指针域是空的&#xff09;。试编写算法判断单链表是否存在环。 #include <iostream>typedef struct node{int data;node* next; }node,*list;list I…

搭建简单docker swarm集群

docker swarm 加入worker 的token docker swarm join-token worker -- worker 加入 docker swarm join-token manager --manager 加入查看集群node docker node ls查看网络 docker network ls创建服务 sudo docker service create --hostname ip:5000/image:latest image …

力扣:98. 验证二叉搜索树

深度优先搜索的中序遍历&#xff1a; 1.先声明一个集合和栈来进行树的遍历存储和模拟树的遍历过程。二叉搜索树的中序遍历结果是一个逐级递增的集合。 2.用一个for循环来进行检查集合是否是一个逐级递增的集合&#xff0c;不是返回false&#xff0c;是放回true。 /*** Defin…

ssh-Ubuntu

一、在Ubuntu端&#xff1a; 1.首先需要安装SSH服务器&#xff0c;在ubuntu终端输入以下指令 sudo apt-get install ssh 1 在这里插入图片描述 2.输入你的ubuntu系统的密码&#xff0c;根据提示输入 “Yes” 或者 “Y” 注意&#xff1a;这里输入密码时&#xff0c;不会显示…

递归方法猴子吃桃问题

public class A {public static void main(String[] args) {System.out.println("第一天有&#xff1a;"f(1)"个");System.out.println("第二天有&#xff1a;"f(2)"个");System.out.println(".....");System.out.println(&…