如何基于Clang C++ API重写宏调用并保留参数注释?
解决Clang宏展开三元表达式重写的错乱问题
你的问题核心是没有正确处理宏展开后的AST节点:函数式宏调用展开后,AST中会保留原宏调用的外壳(比如CallExpr),如果直接遍历这个节点而不是深入到展开后的ConditionalOperator,就会导致宏名残留、结构错乱。结合需求(保留注释+插桩),给你一套可行的实现方案:
1. 先拿到完全展开的AST节点
宏展开后的表达式节点会带有宏位置标记,你需要跳过原宏调用节点,直接获取展开后的实际表达式:
- 用
SourceManager::isMacroCallExpansion()判断当前节点是否来自宏调用 - 调用
Preprocessor::ExpandMacro()把宏调用完整展开成Token流,再重新解析为纯净的AST节点——这样就能彻底摆脱原宏名的干扰
2. 宏参数注释要在预处理阶段抓
Clang的注释不会存在于主AST节点里,必须在预处理时收集:
- 继承
PPCallbacks写个回调类,在MacroExpands方法里遍历宏实参的Token流,把所有注释(AnnotationToken类型)的内容和位置存下来 - 重写表达式时,根据注释的位置判断它属于哪个实参,在生成对应代码时插入注释
3. 统一三元表达式的重写逻辑
不管表达式是不是宏展开来的,都递归处理ConditionalOperator的三个部分:
- 分别重写条件、真分支、假分支的表达式
- 按照要求在外层套上插桩注释
/*PRB #0*/,再把三部分拼接成正确的结构
4. 避免宏名残留的细节
- 遍历AST时,只要遇到宏调用生成的节点,直接替换为展开后的表达式内容,绝对不要输出原宏名
- 重写时用
printPretty()输出节点代码,不要手动拼接宏相关的内容
关键代码片段
获取展开后的表达式节点
#include "clang/AST/ASTContext.h" #include "clang/Lex/Preprocessor.h" using namespace clang; Expr* getExpandedExpr(Expr* E, ASTContext& Ctx, Preprocessor& PP) { SourceManager& SM = Ctx.getSourceManager(); // 判断当前节点是否是宏调用展开的 if (!SM.isMacroCallExpansion(E->getBeginLoc())) { return E; } // 获取宏展开的完整范围,重新解析表达式 SourceRange expansionRange = SM.getExpansionRange(E->getSourceRange()); Token startTok; PP.LexFromRawLexer(startTok, expansionRange.getBegin()); PP.EnterTokenStream(startTok, false); return PP.ParseExpression(); }
重写三元表达式并插入注释
// 保存预处理阶段收集的注释:内容+位置 using CommentPair = std::pair<std::string, SourceLocation>; void rewriteConditional(ConditionalOperator* CO, ASTContext& Ctx, Preprocessor& PP, raw_ostream& OS, const std::vector<CommentPair>& comments) { OS << "(/*PRB #0*/ ("; // 重写条件部分,先获取展开后的节点 rewriteExpr(getExpandedExpr(CO->getCond(), Ctx, PP), Ctx, PP, OS, comments); OS << ") ? ("; // 重写真分支 rewriteExpr(getExpandedExpr(CO->getTrueExpr(), Ctx, PP), Ctx, PP, OS, comments); OS << ") : ("; // 重写假分支 rewriteExpr(getExpandedExpr(CO->getFalseExpr(), Ctx, PP), Ctx, PP, OS, comments); OS << ")))"; } void rewriteExpr(Expr* E, ASTContext& Ctx, Preprocessor& PP, raw_ostream& OS, const std::vector<CommentPair>& comments) { // 先插入当前表达式范围内的注释 SourceRange exprRange = E->getSourceRange(); for (const auto& [commentText, commentLoc] : comments) { if (Ctx.getSourceManager().isPointWithin(exprRange, commentLoc)) { OS << commentText; } } // 处理三元表达式 if (auto* CO = dyn_cast<ConditionalOperator>(E)) { rewriteConditional(CO, Ctx, PP, OS, comments); return; } // 其他表达式类型的默认输出 E->printPretty(OS, nullptr, PrintingPolicy(Ctx.getLangOpts())); }
预处理阶段收集注释的回调
class CommentCollector : public PPCallbacks { public: std::vector<CommentPair> collectedComments; void MacroExpands(const Token& macroNameTok, const MacroDefinition& md, SourceRange range, const MacroArgs* args) override { if (!args) return; // 遍历每个宏实参的Token流 for (unsigned i = 0; i < args->getNumArguments(); ++i) { TokenArg* arg = args->getUnexpArgument(i); for (const Token& tok : arg->tokens()) { // 判断是不是注释Token if (tok.isAnnotationToken() && tok.getAnnotationKind() == AnnotationKind::Comment) { collectedComments.emplace_back(tok.getAnnotationText(), tok.getLocation()); } } } } };
内容的提问来源于stack exchange,提问作者johnco3
相关产品推荐
相关产品推荐

