clang reflection
生成注释
假设有下面的源码:
struct Vec3 { float x, y, z; }; struct Vec4 { float x, y, z, w; };
生成这样的代码:
//[[CLASS INFO]] class:Vec3, is pod:true, is aggregate:true struct Vec3 { float x, y, z; }; //[[CLASS INFO]] use struct-binding method : //const auto &[_m0,_m1,_m2] = Vec3; //[[CLASS INFO]] class:Vec4, is pod:true, is aggregate:true struct Vec4 { float x, y, z, w; }; //[[CLASS INFO]] use struct-binding method : //const auto &[_m0,_m1,_m2,_m3] = Vec4;
#include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/Frontend/ASTUnit.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PreprocessorOutputOptions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/raw_ostream.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Rewrite/Core/Rewriter.h" #include <format> #include <iostream> using namespace clang; class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> { public: MyASTVisitor(Rewriter &w ) : writer{w}{} bool VisitCXXRecordDecl(CXXRecordDecl *declaration) { auto sourceBeginLoc = declaration->getSourceRange().getBegin(); auto sourceEndLoc = declaration->getSourceRange().getEnd(); declaration->dump();
auto name = declaration->getNameAsString(); bool isPod = declaration->isPOD(); auto isAggregate = declaration->isAggregate(); auto fieldCount = std::distance(declaration->field_begin(), declaration->field_end()); std::string fieldBinding{"const auto &["}; for(auto i=0;i<fieldCount;i++){ fieldBinding += "_m" + std::to_string(i); if(i!=fieldCount-1) fieldBinding += ","; } fieldBinding += "] = "; fieldBinding += name; auto classInfo = std::format("//[[CLASS INFO]] class:{}, is pod:{}, is aggregate:{}\n", name, isPod, isAggregate); auto howToBind = std::format("\n//[[CLASS INFO]] use struct-binding method : \n//{};\n\n", fieldBinding); writer.InsertText(sourceBeginLoc, classInfo, true, true); writer.InsertText(declaration->getEndLoc().getLocWithOffset(2), howToBind,true, true); // location使用sourceEndLoc变量也可以 return true; } private: Rewriter &writer; }; class MyASTConsumer : public ASTConsumer { public: MyASTConsumer(Rewriter &w) : writer{w}{} void HandleTranslationUnit(ASTContext &context) override { MyASTVisitor visitor{writer}; visitor.TraverseDecl(context.getTranslationUnitDecl()); } Rewriter &writer; }; class MyFrontendAction : public ASTFrontendAction { public: void EndSourceFileAction() override{ auto &sm = writer.getSourceMgr(); writer.getEditBuffer(sm.getMainFileID()).write(llvm::outs()); } std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &compiler, StringRef file) override { llvm::outs() << "create ast consumer for: " << file << "\n"; writer.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts()); return std::make_unique<MyASTConsumer>(writer); } private: Rewriter writer; }; int main(int argc, const char **argv) { auto code= R"( struct Vec3 { float x, y, z; }; struct Vec4 { float x, y, z, w; }; )"; clang::tooling::runToolOnCode(std::make_unique<MyFrontendAction>(), code); return 0; }
输出:
D:\Plugin_Dev\CPP\LLVM\modify_ast\cmake-build-debug\xxx.exe create ast consumer for: input.cc CXXRecordDecl 0x23cdd3e7c48 <input.cc:3:1, line:5:1> line:3:8 struct Vec3 definition |-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable pod trivial literal | |-DefaultConstructor exists trivial needs_implicit | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param | |-MoveConstructor exists simple trivial needs_implicit | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param | |-MoveAssignment exists simple trivial needs_implicit | `-Destructor simple irrelevant trivial needs_implicit |-CXXRecordDecl 0x23cdd3e7d68 <col:1, col:8> col:8 implicit struct Vec3 |-FieldDecl 0x23cdd3e7e10 <line:4:3, col:9> col:9 x 'float' |-FieldDecl 0x23cdd3e7e80 <col:3, col:12> col:12 y 'float' `-FieldDecl 0x23cdd3e7ef0 <col:3, col:15> col:15 z 'float' CXXRecordDecl 0x23cdd3e7f80 <input.cc:7:1, line:9:1> line:7:8 struct Vec4 definition |-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable pod trivial literal | |-DefaultConstructor exists trivial needs_implicit | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param | |-MoveConstructor exists simple trivial needs_implicit | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param | |-MoveAssignment exists simple trivial needs_implicit | `-Destructor simple irrelevant trivial needs_implicit |-CXXRecordDecl 0x23cdd3e8098 <col:1, col:8> col:8 implicit struct Vec4 |-FieldDecl 0x23cdd3e8140 <line:8:3, col:9> col:9 x 'float' |-FieldDecl 0x23cdd3e81b0 <col:3, col:12> col:12 y 'float' |-FieldDecl 0x23cdd3e8220 <col:3, col:15> col:15 z 'float' `-FieldDecl 0x23cdd3e8290 <col:3, col:18> col:18 w 'float' //[[CLASS INFO]] class:Vec3, is pod:true, is aggregate:true struct Vec3 { float x, y, z; }; //[[CLASS INFO]] use struct-binding method : //const auto &[_m0,_m1,_m2] = Vec3; //[[CLASS INFO]] class:Vec4, is pod:true, is aggregate:true struct Vec4 { float x, y, z, w; }; //[[CLASS INFO]] use struct-binding method : //const auto &[_m0,_m1,_m2,_m3] = Vec4; Process finished with exit code 0
这句话输出的结果:
writer.getEditBuffer(sm.getMainFileID()).write(llvm::outs());
第一种方法输出到std::string
auto &sm = writer.getSourceMgr(); auto &buffer = writer.getEditBuffer(sm.getMainFileID()); std::string s; llvm::raw_string_ostream os(s); buffer.write(os); std::cout << s << std::endl;
第二种:
auto startLoc = sm.getLocForStartOfFile(sm.getMainFileID()); auto endLoc = sm.getLocForEndOfFile(sm.getMainFileID()); SourceRange const range(startLoc, endLoc); std::string const output_code = writer.getRewrittenText(range); std::cout << output_code << std::endl;
还有一个是输出到文件,这里用的llvm输出流 c++ - Clang using LibTooling Rewriter to generate new file? - Stack Overflow
// For each source file provided to the tool, a new FrontendAction is created. class MyFrontendAction : public ASTFrontendAction { public: MyFrontendAction() {} void EndSourceFileAction() override { SourceManager &SM = TheRewriter.getSourceMgr(); llvm::errs() << "** EndSourceFileAction for: " << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n"; // Now emit the rewritten buffer. // TheRewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs()); --> this will output to screen as what you got. std::error_code error_code; llvm::raw_fd_ostream outFile("output.txt", error_code, llvm::sys::fs::F_None); TheRewriter.getEditBuffer(SM.getMainFileID()).write(outFile); // --> this will write the result to outFile outFile.close(); } //as normal, make sure it matches your ASTConsumer constructor std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) override { llvm::errs() << "** Creating AST consumer for: " << file << "\n"; TheRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); return llvm::make_unique<MyASTConsumer>(TheRewriter,&CI.getASTContext()); }
反射
我们网易自研了一个软件。核心中的代码反射到GUI空间,【是通过函数式编程实现。类似HDK,这个是传统方案】。
V2我将会 实现了3种方式:
1. 函数式编程硬写,写5行可能就GUI就展示5个界面
2. json 字符串,直接反序列化,本质还是用函数式编程,然后就到1这个步骤。
3. 这个方法就是接下来的方法:
定义一个宏,这个宏本质式空的,通过clang展开代码。然后生成新的CPP代码,再次link进去compile.
qt moc.exe也是这个本质,生成一个新代码,需要link xxx.moc.cpp
我把生成的代码直接通过原本函数式编程注进去
1. clang/gnu __attribute__ 抽取REFLECTED信息:
对于下面的类,注意下面的语句只有clang/gnu ast支持,msvc并不支持。但是在msvc编译器下用clang前端可以分析这样的代码。(token,name 模拟了HDK的参数)
#define GUD_REFLECTED(token,name) __attribute__((annotate("reflected"), annotate(#token), annotate(#name))) struct Vec3 { GUD_REFLECTED(chan0, value0) float x; GUD_REFLECTED(chan0, value1) float y; GUD_REFLECTED(chan0, value2) float z; }; struct Vec4 { float x, y, z, w; };
Vec3反射了3个变量。
Vec4反射0个变量。
在编译这个时候,抽取这个宏,然后,做一些事,首先先得到这个AST:
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { std::cout << "for class:" << Declaration->getNameAsString() << "\n"; for (const auto *field : Declaration->fields()) { std::string fieldName = field->getNameAsString(); std::cout << "for field:" << fieldName << ":"; for(auto *attr: field->attrs() ){ if (not AnnotateAttr::classof(attr)) continue; const auto *annotate_att = static_cast<const AnnotateAttr *>(attr); std::string str = annotate_att->getAnnotation().str(); std::cout << str<< " "; } std::cout << "\n"; } std::cout << "\n"; // new line for every class return true; }
源码:
#include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/Frontend/ASTUnit.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PreprocessorOutputOptions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/raw_ostream.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/AST/Comment.h" #include <format> #include <iostream> using namespace clang; class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> { public: bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { std::cout << "for class:" << Declaration->getNameAsString() << "\n"; for (const auto *field : Declaration->fields()) { std::string fieldName = field->getNameAsString(); std::cout << "for field:" << fieldName << ":"; for(auto *attr: field->attrs() ){ if (not clang::AnnotateAttr::classof(attr)) continue; const auto *annotate_att = static_cast<const AnnotateAttr *>(attr); std::string str = annotate_att->getAnnotation().str(); std::cout << str<< " "; } std::cout << "\n"; } std::cout << "\n"; // new line for every class return true; } }; class MyASTConsumer : public ASTConsumer { public: void HandleTranslationUnit(ASTContext &context) override { MyASTVisitor visitor{}; visitor.TraverseDecl(context.getTranslationUnitDecl()); } }; class MyFrontendAction : public ASTFrontendAction { public: void EndSourceFileAction() override{ auto &sm = writer.getSourceMgr(); auto &buffer = writer.getEditBuffer(sm.getMainFileID()); buffer.write(llvm::outs()); } std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &compiler, StringRef file) override { llvm::outs() << "create ast consumer for: " << file << "\n"; compiler.getLangOpts().CPlusPlus = true; compiler.getPreprocessorOpts().addMacroDef("__cplusplus"); compiler.getPreprocessorOpts().addMacroDef("Vec3=struct Vec3"); compiler.getLangOpts().CPlusPlus2b = true; writer.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts()); return std::make_unique<MyASTConsumer>(); } private: Rewriter writer; }; /* #define GUD_REFLECTED(token,name) __attribute__((annotate("reflected"), annotate(#token), annotate(#name))) struct test{ GUD_REFLECTED(xxx) float x; };*/ int main(int argc, const char **argv) { auto code= R"( #define GUD_REFLECTED(token,name) __attribute__((annotate("reflected"), annotate(#token), annotate(#name))) struct Vec3 { GUD_REFLECTED(chan0, value0) float x; GUD_REFLECTED(chan0, value1) float y; GUD_REFLECTED(chan0, value2) float z; GUD_REFLECTED(chan0, value3) float w; }; struct Vec4 { float x, y, z, w; }; )"; clang::tooling::runToolOnCode(std::make_unique<MyFrontendAction>(), code); return 0; }
执行结果:
create ast consumer for: input.cc for class:Vec3 for field:x:reflected chan0 value0 for field:y:reflected chan0 value1 for field:z:reflected chan0 value2 for class:Vec4 for field:x: for field:y: for field:z: for field:w:
有了这个结果,本质可以做很多事情。比如要做成json还是xml 还是 做成特殊的结构。
2. 为了支持window平台,因为代码仅仅使用clang做parse.
所以只要客户端代码是:
#if _GUD_CLANG_REFLECTED #define GUD_REFLECTED(...) __attribute__((annotate(#__VA_ARGS__))) #else #define GUD_REFLECTED(...) #endif struct Vec3 { GUD_REFLECTED(chan0, value0) float x; GUD_REFLECTED(chan0, value1) float y; GUD_REFLECTED(chan0, value2) float z; }; struct Vec4 { float x, y, z, w; };
然后在跑clang的时候:
int main(){ std::vector<std::string> CommandLineArgs = {"-fsyntax-only", "-D_GUD_CLANG_REFLECTED"}; clang::tooling::runToolOnCodeWithArgs(std::make_unique<MyFrontendAction>(), code, CommandLineArgs); }
3. 对类如法炮制:
bool VisitCXXRecordDecl(CXXRecordDecl *decl) { std::cout << "[[CLASS]]:" <<decl->getNameAsString() << " REFLECTED INFO:" << std::endl; for(auto attr :decl->attrs()){ if (not clang::AnnotateAttr::classof(attr)) continue; const auto *annotate_att = static_cast<const AnnotateAttr *>(attr); std::string str = annotate_att->getAnnotation().str(); std::cout << str<< " " ; } std::cout << "\n"; return true; }
此时源码就会成为:
#if _GUD_CLANG_REFLECTED #define GUD_REFLECTED_FIELD(...) __attribute__((annotate(#__VA_ARGS__))) #define GUD_REFLECTED_CLASS(...) class __attribute__((annotate(#__VA_ARGS__))) #define GUD_REFLECTED_STRUCT(...) struct __attribute__((annotate(#__VA_ARGS__))) #else #define GUD_REFLECTED_FIELD(...) #define GUD_REFLECTED_CLASS(...) #define GUD_REFLECTED_STRUCT(...) #endif GUD_REFLECTED_STRUCT(Vec3, simple_vector3) Vec3 { GUD_REFLECTED_FIELD(chan0,value0) float x; GUD_REFLECTED_FIELD(chan0,value1) float y; GUD_REFLECTED_FIELD(chan0,value2) float z; }; GUD_REFLECTED_STRUCT(Vec4, simple_vector4) Vec4 { float x, y, z, w; };
输出:
[[CLASS]]:Vec3 REFLECTED INFO: Vec3, simple_vector3 [[CLASS]]:Vec4 REFLECTED INFO: Vec4, simple_vector4
参考: