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

LLVM IR数组索引检测插桩:动态数组适配方案咨询

适配静态/动态数组的多维索引检测与插桩方案

你之前用LLVM IR层面的getelementptr方案只适配静态数组的问题,核心原因是动态数组在IR层面会被处理为指针操作,没有数组类型的元信息。而Clang AST匹配器是更合适的方案——它直接在抽象语法树层面捕获源代码中的[]索引语法,不管底层是静态数组还是动态指针数组,都能统一处理。

实现思路

源代码中的array[i][j][k]在AST中表现为嵌套的ArraySubscriptExpr节点:最外层是下标为k的ArraySubscriptExpr,其基表达式是下标为j的ArraySubscriptExpr,再内层是下标为i的ArraySubscriptExpr,最底层是数组变量的引用。

我们可以通过以下步骤实现插桩:

  1. 用AST匹配器捕获所有ArraySubscriptExpr节点;
  2. 递归遍历嵌套的ArraySubscriptExpr,收集所有下标表达式和原始数组变量;
  3. 根据索引操作的上下文(独立语句/表达式的一部分),插入或替换为instrument_array_indexing调用,保证语法正确性。

代码示例(基于Clang Tooling)

#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"

using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;

class ArrayIndexInstrumenter : public MatchFinder::MatchCallback {
public:
  ArrayIndexInstrumenter(Rewriter &R) : Rewrite(R) {}

  void run(const MatchFinder::MatchResult &Result) override {
    const ArraySubscriptExpr *outerASE = Result.Nodes.getNodeAs<ArraySubscriptExpr>("arraySubscript");
    if (!outerASE || Result.Context->getDiagnostics().hasErrorOccurred()) {
      return;
    }

    // 递归收集所有下标和原始数组
    SmallVector<const Expr*, 4> indices;
    const Expr *currentBase = outerASE->getBase();
    indices.push_back(outerASE->getIndex());

    // 遍历嵌套的ArraySubscriptExpr
    while (const ArraySubscriptExpr *innerASE = dyn_cast<ArraySubscriptExpr>(currentBase)) {
      indices.push_back(innerASE->getIndex());
      currentBase = innerASE->getBase();
    }
    // 反转下标顺序,从最内层到最外层
    std::reverse(indices.begin(), indices.end());
    const Expr *arrayExpr = currentBase;

    // 处理带括号的数组表达式
    if (const ParenExpr *parenExpr = dyn_cast<ParenExpr>(arrayExpr)) {
      arrayExpr = parenExpr->getSubExpr();
    }

    // 生成instrument_array_indexing的调用字符串
    std::string callStr = "instrument_array_indexing(";
    callStr += Rewrite.getRewrittenText(arrayExpr->getSourceRange());
    for (const Expr *idx : indices) {
      callStr += ", " + Rewrite.getRewrittenText(idx->getSourceRange());
    }
    callStr += ")";

    // 判断当前索引操作是否是独立语句
    bool isStandaloneStmt = isa<ExprStmt>(outerASE->getParent());

    if (isStandaloneStmt) {
      // 独立语句:在原语句前插桩调用
      SourceLocation insertLoc = outerASE->getBeginLoc();
      Rewrite.InsertText(insertLoc, callStr + ";\n", true, true);
    } else {
      // 作为表达式的一部分:用逗号表达式包裹,保留原表达式值
      std::string replacement = "(" + callStr + ", " + Rewrite.getRewrittenText(outerASE->getSourceRange()) + ")";
      Rewrite.ReplaceText(outerASE->getSourceRange(), replacement);
    }
  }

private:
  Rewriter &Rewrite;
};

static llvm::cl::OptionCategory ToolCategory("Array Index Instrumenter Options");

int main(int argc, const char **argv) {
  CommonOptionsParser optionsParser(argc, argv, ToolCategory);
  ClangTool tool(optionsParser.getCompilations(), optionsParser.getSourcePathList());

  Rewriter rewriter;
  ArrayIndexInstrumenter instrumenter(rewriter);
  MatchFinder finder;

  // 匹配所有ArraySubscriptExpr节点
  finder.addMatcher(arraySubscriptExpr().bind("arraySubscript"), &instrumenter);

  // 运行工具并初始化Rewriter
  int result = tool.run(newFrontendActionFactory(&finder, [&rewriter](CompilerInstance &CI) {
    rewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
  }).get());

  // 将修改写入文件(或输出到控制台)
  if (result == 0) {
    for (const auto &filePath : optionsParser.getSourcePathList()) {
      const FileEntry *file = rewriter.getSourceMgr().getFileEntryForPath(filePath);
      if (file) {
        SourceLocation fileStart = rewriter.getSourceMgr().getLocForStartOfFile(rewriter.getSourceMgr().getMainFileID());
        rewriter.getEditBuffer(rewriter.getSourceMgr().getMainFileID()).write(llvm::outs());
      }
    }
  }

  return result;
}

关键优势

  1. 统一处理静态/动态数组:不管是int arr[3][4]这类静态数组,还是int **arr = new int*[3]这类动态指针数组,只要源代码中使用[]索引,都会被ArraySubscriptExpr节点捕获。
  2. 贴近源代码结构:直接在AST层面操作,不需要关心IR层面的编译转换细节,能准确还原源代码中的下标表达式(比如arr[i+1][j*2]这类复杂下标)。
  3. 语法安全:根据索引操作的上下文选择插桩方式,避免在函数参数、赋值表达式中间插入语句导致的语法错误。

注意事项

  • 确保instrument_array_indexing函数已经在代码中声明或包含头文件,避免编译错误。
  • 可以通过匹配器的过滤条件(比如hasBase(hasType(PointerType()))或hasBase(hasType(ConstantArrayType())))针对特定类型的数组进行插桩。
  • 对于更复杂的表达式(比如链式调用后的索引obj.getArray()[i][j]),代码会自动收集完整的基表达式,无需额外修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:10:20