You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用VisitCallExpr捕获模板函数实例化中myFunction的所有调用?

问题

我想要查找函数myFunction的所有调用,示例代码如下:

void myFunction(int value) {

}

template <typename T>
void wrapper(T value) {
  myFunction(value);
}

int
main(int argc, char const* argv[]) {
  wrapper<int>(42);
  return 0;
}

我的访问器代码如下:

bool VisitCallExpr(CallExpr *Expr) {
    if (Expr) {
      Expr->dump();
      auto decl = dyn_cast_or_null<FunctionDecl>(Expr->getCalleeDecl());
      if (decl) {
        decl->dump();
      }
    }
    return true;
}

当前访问器找到了两个调用:模板函数wrapper中的myFunction调用,以及wrapper自身的调用。由于myFunction是从模板函数中调用的,其AST似乎不完整。我需要获取wrapper每个具体实例化调用对应的AST。

当前输出的AST信息如下:

CallExpr 0x564a42a6c018 '<dependent type>'
|-UnresolvedLookupExpr 0x564a42a6bfb0 '<overloaded function type>' lvalue (ADL) = 'myFunction' 0x564a42a6bb68
`-DeclRefExpr 0x564a42a6bff8 'T' lvalue ParmVar 0x564a42a6bd40 'value' 'T'
CallExpr 0x564a42a6c7c0 'void'
|-ImplicitCastExpr 0x564a42a6c7a8 'void (*)(int)' <FunctionToPointerDecay>
| `-DeclRefExpr 0x564a42a6c6f8 'void (int)' lvalue Function 0x564a42a6c5f8 'wrapper' 'void (int)' (FunctionTemplate 0x564a42a6bed8 'wrapper')
`-IntegerLiteral 0x564a42a6c418 'int' 42

查看wrapper调用的函数声明时,确实存在我需要的CallExpr节点:

FunctionDecl 0x564a42a6c5f8 <...> line:510:6 used wrapper 'void (int)'
|-TemplateArgument type 'int'
| `-BuiltinType 0x564a42a24270 'int'
|-ParmVarDecl 0x564a42a6c538 <col:14, col:16> col:16 used value 'int':'int'
`-CompoundStmt 0x564a42a6c918 <col:23, line:512:1>
  `-CallExpr 0x564a42a6c8d8 <line:511:3, col:19> 'void'
    |-ImplicitCastExpr 0x564a42a6c8c0 <col:3> 'void (*)(int)' <FunctionToPointerDecay>
    | `-DeclRefExpr 0x564a42a6c8a0 <col:3> 'void (int)' lvalue Function 0x564a42a6bb68 'myFunction' 'void (int)'
    `-ImplicitCastExpr 0x564a42a6c900 <col:14> 'int':'int' <LValueToRValue>
      `-DeclRefExpr 0x564a42a6c880 <col:14> 'int':'int' lvalue ParmVar 0x564a42a6c538 'value' 'int':'int'

但该节点从未被访问器遍历到,不清楚问题出在哪里,希望得到帮助。

解决方案

问题核心是Clang默认AST遍历不会自动处理模板实例化后的函数体——原始模板和实例化特化是两个独立的AST节点,默认只遍历原始模板。要获取实例化版本里的CallExpr,按以下步骤调整:

  • 启用模板实例化的AST生成
    编译时添加-fno-delayed-template-parsing选项,关闭Clang的延迟模板解析功能,确保模板实例化时立即生成完整的函数体AST,而非延迟到需要时才处理。

  • 扩展访问器,处理模板实例化的FunctionDecl
    仅靠VisitCallExpr无法覆盖实例化函数内的调用,需要添加对FunctionDecl的处理:

    1. 判断当前FunctionDecl是否是模板实例化生成的(调用isTemplateInstantiation()方法)。
    2. 如果是,手动遍历该函数体的所有子节点,捕获其中的CallExpr。

修改后的访问器示例:

bool VisitFunctionDecl(FunctionDecl *Func) {
    if (Func && Func->isTemplateInstantiation()) {
        Stmt *Body = Func->getBody();
        if (Body) {
            for (auto *Child : Body->children()) {
                if (auto *Call = dyn_cast<CallExpr>(Child)) {
                    Call->dump();
                    auto CalleeDecl = dyn_cast_or_null<FunctionDecl>(Call->getCalleeDecl());
                    if (CalleeDecl) {
                        CalleeDecl->dump();
                    }
                }
            }
        }
    }
    return true;
}

bool VisitCallExpr(CallExpr *Expr) {
    if (Expr) {
      Expr->dump();
      auto decl = dyn_cast_or_null<FunctionDecl>(Expr->getCalleeDecl());
      if (decl) {
        decl->dump();
      }
    }
    return true;
}
  • 区分依赖与非依赖调用节点
    原始模板中的myFunction调用是未解析的依赖表达式(UnresolvedLookupExpr),而实例化后的调用是已解析的DeclRefExpr。可以通过检查Expr->getCalleeDecl()是否非空,过滤掉无效的依赖调用,只处理实例化后的有效调用。

内容的提问来源于stack exchange,提问作者yayj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 05:45:40