fccf: 快速C/C++代码查找器
fccf
是一个命令行工具,可以根据搜索字符串快速搜索目录中的C/C++源代码,并打印与查询匹配的相关代码片段。
特点
- 快速识别包含搜索字符串的源文件。
- 为每个候选源文件构建抽象语法树(AST)。
- 访问AST中的节点,寻找与用户请求匹配的函数声明、类、枚举、变量等。
- 将识别到的源代码片段美化打印到终端。
- MIT许可证
搜索Linux内核源代码树
以下视频展示了fccf
在torvalds/linux中搜索并找到代码片段。
https://user-images.githubusercontent.com/8450091/165400381-9ba49a62-97fb-4f4a-890a-0dc9b20dfe75.mp4
搜索fccf
源代码树(现代C++)
以下视频展示了fccf
搜索fccf
C++源代码。
注意,这里的搜索结果包括:
- 类声明
- 函数和函数模板
- 变量声明,包括lambda函数
https://user-images.githubusercontent.com/8450091/165402206-65d9ed43-b9dd-4528-92bd-0b4ce76b6468.mp4
搜索任何匹配的--flag
提供一个空查询以匹配任何--flag
,例如任何枚举声明。
...或任何类构造函数
搜索for
语句
使用--for-statement
来搜索for
语句。fccf
将尝试找到包含查询字符串的for
语句(包括C++范围for
)。
搜索表达式
使用--include-expressions
选项来查找与查询匹配的表达式。
以下示例展示了fccf
查找对isdigit()
的调用。
以下示例展示了fccf
查找对clang_options
变量的引用。
搜索using
声明
使用--using-declaration
选项来查找using
声明、using
指令和类型别名声明。
搜索namespace
别名
使用--namespace-alias
选项来查找namespace
别名。
搜索throw
表达式
使用--throw-expression
和查询字符串来搜索包含该查询字符串的特定throw
表达式。
如前所述,这里的空查询将尝试匹配代码库中的任何throw
表达式:
构建说明
使用CMake构建fccf
。更多详情,请参见BUILDING.md。
注意:fccf
需要安装libclang
和LLVM
。
# 安装libclang和LLVM
# sudo apt install libclang-dev llvm
git clone https://github.com/p-ranav/fccf
cd fccf
# 构建
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build
# 安装
sudo cmake --install build
fccf
使用方法
foo@bar:~$ fccf --help
用法: fccf [--help] [--version] [--help] [--exact-match] [--json] [--filter VAR] [-j VAR] [--enum] [--struct] [--union] [--member-function] [--function] [--function-template] [-F] [--class] [--class-template] [--class-constructor] [--class-destructor] [-C] [--for-statement] [--namespace-alias] [--parameter-declaration] [--typedef] [--using-declaration] [--variable-declaration] [--verbose] [--include-expressions] [--static-cast] [--dynamic-cast] [--reinterpret-cast] [--const-cast] [-c] [--throw-expression] [--ignore-single-line-results] [--include-dir VAR]... [--language VAR] [--std VAR] [--no-color] query [path]...
位置参数:
query
path [参数数量: 0或更多]
可选参数:
-h, --help 显示帮助信息并退出
-v, --version 打印版本信息并退出
-h, --help 显示帮助信息并退出
-E, --exact-match 仅考虑精确匹配
--json 以JSON格式打印结果
-f, --filter 仅评估匹配过滤器模式的文件 [参数数量=0..1] [默认值: "."]
-j 线程数 [参数数量=0..1] [默认值: 5]
--enum 搜索枚举声明
--struct 搜索结构体声明
--union 搜索联合体声明
--member-function 搜索类成员函数声明
--function 搜索函数声明
--function-template 搜索函数模板声明
-F 搜索任何函数、函数模板或类成员函数
--class 搜索类声明
--class-template 搜索类模板声明
--class-constructor 搜索类构造函数声明
--class-destructor 搜索类析构函数声明
-C 搜索任何类、类模板或结构体
--for-statement 搜索for
语句
--namespace-alias 搜索命名空间别名
--parameter-declaration 搜索函数或方法参数
--typedef 搜索typedef声明
--using-declaration 搜索using声明、using指令和类型别名声明
--variable-declaration 搜索变量声明
--verbose 请求详细输出
--ie, --include-expressions 搜索引用某些值或成员的表达式,如函数、变量或枚举器
--static-cast 搜索static_cast
--dynamic-cast 搜索dynamic_cast
--reinterpret-cast 搜索reinterpret_cast
--const-cast 搜索const_cast
-c 搜索任何static_cast、dynamic_cast、reinterpret_cast或const_cast表达式
--throw-expression 搜索throw表达式
--isl, --ignore-single-line-results 忽略前向声明、成员函数声明等
-I, --include-dir 附加包含目录 [参数数量=0..1] [默认值: {}] [可重复]
-l, --language clang使用的语言选项 [参数数量=0..1] [默认值: "c++"]
--std clang使用的C++标准 [参数数量=0..1] [默认值: "c++17"]
--nc, --no-color 停止fccf对输出进行着色
工作原理:
fccf
对目录进行递归搜索,在一堆文件中查找目标 - 类似于grep
或ripgrep
- 它尽可能使用SSE2
strstr
SIMD算法(修改的Rabin-Karp SIMD搜索;参见这里)在多个线程中快速找到包含目标的源文件子集。- 对于每个候选源文件,它使用
libclang
解析翻译单元(构建抽象语法树)。 - 然后访问AST中的每个子节点,寻找特定的节点类型,例如用于函数声明的
CXCursor_FunctionDecl
。 - 一旦识别出相关节点,如果节点的"拼写"(
libclang
对节点的命名)与搜索查询匹配,则确定AST节点的源范围 - 源范围是缓冲区中代码片段的起始和结束索引。 - 然后,它美化打印这段代码。我有一个简单的词法分析器,用于对这段代码进行标记化并打印彩色输出。
关于include_directories
的说明:
为了使所有这些工作正常进行,fccf首先识别包含头文件的候选目录,例如以include/
结尾的路径。然后将这些路径作为-Ifoo -Ibar/baz
等添加到clang选项中(在解析翻译单元之前)。此外,对于每个翻译单元,父路径和祖父路径也会被添加到该单元的包含目录中,以增加成功解析的可能性。
还可以使用-I
或--include-dir
选项为fccf
提供额外的包含目录。使用详细输出(--verbose
)可以识别libclang解析中的错误,并尝试进行修复(例如,添加正确的包含目录以使libclang
满意)。
要在没有任何libclang错误的情况下在fccf
源代码上运行fccf
,我必须明确提供来自LLVM-12的包含路径,如下所示:
foo@bar:~$ fccf --verbose 'lexer' . --include-dir /usr/lib/llvm-12/include/
检查 ./source/lexer.cpp
检查 ./source/lexer.hpp
检查 ./source/searcher.cpp
// ./source/lexer.hpp(第14行至第40行)
class lexer
{
std::string_view m_input;
fmt::memory_buffer* m_out;
std::size_t m_index {0};
bool m_is_stdout {true};
char previous() const;
char current() const;
char next() const;
void move_forward(std::size_t n = 1);
bool is_line_comment();
bool is_block_comment();
bool is_start_of_identifier();
bool is_start_of_string();
bool is_start_of_number();
void process_line_comment();
void process_block_comment();
bool process_identifier(bool maybe_class_or_struct = false);
void process_string();
std::size_t get_number_of_characters(std::string_view str);
public:
void tokenize_and_pretty_print(std::string_view source,
fmt::memory_buffer* out,
bool is_stdout = true);
}
参考文献: