基于Clang LibTooling:从FunctionDecl获取令牌序列做函数相似性检查
基于Clang LibTooling提取函数令牌序列实现代码相似性检测
问题背景
开发一款基于Clang LibTooling的工具用于检测相似函数,已能获取clang::FunctionDecl,但基于源码文本的相似性检测精度低、速度慢,希望将函数源码转换为令牌序列,实现类似如下功能:
SomeContainer<Token> tokens = getTokenSequence(funcDecl); for (const auto &t : tokens) // ...
解决方案
Clang提供了原生的Lexer和Token API,可以直接从FunctionDecl提取令牌序列,无需自行实现分词逻辑,具体实现如下:
所需头文件
#include "clang/AST/ASTContext.h" #include "clang/Lex/Lexer.h" #include "clang/Basic/TokenKinds.h"
实现getTokenSequence函数
std::vector<clang::Token> getTokenSequence(const clang::FunctionDecl *funcDecl, clang::ASTContext &ctx) { std::vector<clang::Token> tokens; // 获取函数的完整源范围:覆盖函数声明到函数体结束 clang::SourceRange funcRange( funcDecl->getSourceRange().getBegin(), funcDecl->getBody()->getSourceRange().getEnd() ); if (!funcRange.isValid()) { return tokens; } // 初始化Lexer,绑定到函数所在的源码缓冲区 auto &srcMgr = ctx.getSourceManager(); const char *bufStart = srcMgr.getBufferData(funcRange.getBegin()).data(); const char *bufEnd = bufStart + srcMgr.getBufferSize(funcRange.getBegin()); clang::Lexer lexer( funcRange.getBegin(), ctx.getLangOpts(), bufStart, bufStart, bufEnd ); clang::Token token; // 遍历并收集范围内的所有令牌 while (lexer.LexFromRawLexer(token)) { if (srcMgr.isBeforeInSLocAddrSpace(token.getEndLoc(), funcRange.getEnd())) { // 可选:过滤注释、空白等无关令牌 if (!token.is(clang::tok::comment) && !token.is(clang::tok::unknown)) { tokens.push_back(token); } } else { break; } } return tokens; }
关键说明
- 范围准确性:通过合并函数声明和函数体的SourceRange,确保覆盖整个函数的源码内容
- 令牌过滤:可根据需求跳过注释、空白等无关令牌,减少后续相似性分析的噪音
- 令牌类型利用:通过
token.getKind()可获取令牌的类型(如关键字、标识符、运算符等),后续相似性检测时可忽略标识符名称这类易变内容,只保留语法结构相关的令牌,大幅提升检测精度 - 性能优势:直接基于Clang预处理后的令牌操作,比文本分词速度更快,且避免了文本处理中的语法歧义问题
内容的提问来源于stack exchange,提问作者GKxx
相关产品推荐
相关产品推荐

