如何使用 Clang 找到所有成员字段 read/writes?
How can I find all member field read/writes using Clang?
给定一个 C++ 源代码,我想找到每个函数写入和读取的 class 字段。使用 Clang 前端执行此操作的最佳方法是什么?
(我不是要求对所有步骤进行详细解释;但是,如果能提供一个有效解决方案的起点就更好了。)
到目前为止,我尝试使用 RecursiveASTVisitor 解析语句,但很难跟踪节点连接。另外,我不知道如何跟踪下面的内容:
int& x = m_int_field;
x++;
这显然修改了m_int_field
;但是给定一个 Stmt
是不可能知道的;所以 AST 遍历本身似乎不够。
我的一个好处是能够分别计算字段和子字段(例如访问成员结构的三个字段)。
示例:
typedef struct Y {
int m_structfield1;
float m_structfield2;
Y () {
m_structfield1 = 0;
m_structfield2 = 1.0f;
}
} Y;
class X {
int m_field1;
std::string m_field2;
Y m_field3;
public:
X () : m_field2("lel") {}
virtual ~X() {}
void func1 (std::string s) {
m_field1 += 2;
m_field2 = s;
}
int func2 () {
return m_field1 + 5;
}
void func3 (Y& y) {
int& i = m_field1;
y.m_structfield2 = 1.2f + i++;
}
int func4 () {
func3 (m_field3);
return m_field3.m_structfield1;
}
};
应该return
X::X() -> m_field1 (w), m_field3.m_structfield1 (w), m_field3.m_structfield2 (w)
X::func1(std::string) -> m_field1 (r+w), m_field2 (w)
X::func2() -> m_field1 (r)
X::func3(Y&) -> m_field1 (r+w)
X::func4() -> m_field1 (r+w), m_field3.m_structfield2 (w), m_field3.m_structfield1 (r)
为简单起见,我们可以假设没有继承。
我一直在收集一些 examples of analyzing code with Clang's AST matchers。那里有一个示例应用程序 StructFieldUser,它报告结构的哪些字段被读取或写入,以及每次访问发生在哪个函数中。它与您正在寻找的不同,但它可能是一个有用的参考点。它演示了提取和记录此类信息,并说明了如何将所有部分放在一起。
一般来说,开始使用 AST 匹配器的好地方是 this post by Eli Bendersky。
要了解可以解决您问题的匹配器,您可以练习 clang-query
:
$ clang-query example.cpp -- # the two dashes mean no compilation db
clang-query> let m1 memberExpr()
clang-query> m m1
Match #1:
/path/example.cpp:9:9: note: "root" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
Match #2:
/path/example.cpp:10:9: note: "root" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
...
11 matches.
然后你可以开始使用遍历匹配器连接到其他节点。这使您可以捕获相关的上下文,例如在其中进行引用的函数或 class 方法。将 bind
表达式添加到节点匹配器将帮助您准确了解匹配的内容。绑定节点还将提供对回调中节点的访问权限。
clang-query> let m2 memberExpr(hasAncestor(functionDecl().bind("fdecl"))).bind("mexpr")
clang-query> m m2
Match #1:
/path/example.cpp/path/example.cpp:8:5: note: "fdecl" binds here
Y () {
^~~~~~
/path/example.cpp:9:9: note: "mexpr" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
/path/example.cpp:9:9: note: "root" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
Match #2:
/path/example.cpp:8:5: note: "fdecl" binds here
Y () {
^~~~~~
/path/example.cpp:10:9: note: "mexpr" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
/path/example.cpp:10:9: note: "root" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
...
可能需要一些工作才能了解如何挑选您需要的确切节点。请注意,上面的匹配器不会在 X::X()
中进行初始化。从
看AST
clang-check -ast-dump example.cpp --
表明那些节点不是MemberExpr
节点;它们是 CXXCtorInitializer
个节点,因此需要 cxxCtorInitializer
匹配器来获取这些节点。可能需要多个匹配器才能找到所有不同的节点。
给定一个 C++ 源代码,我想找到每个函数写入和读取的 class 字段。使用 Clang 前端执行此操作的最佳方法是什么?
(我不是要求对所有步骤进行详细解释;但是,如果能提供一个有效解决方案的起点就更好了。)
到目前为止,我尝试使用 RecursiveASTVisitor 解析语句,但很难跟踪节点连接。另外,我不知道如何跟踪下面的内容:
int& x = m_int_field;
x++;
这显然修改了m_int_field
;但是给定一个 Stmt
是不可能知道的;所以 AST 遍历本身似乎不够。
我的一个好处是能够分别计算字段和子字段(例如访问成员结构的三个字段)。
示例:
typedef struct Y {
int m_structfield1;
float m_structfield2;
Y () {
m_structfield1 = 0;
m_structfield2 = 1.0f;
}
} Y;
class X {
int m_field1;
std::string m_field2;
Y m_field3;
public:
X () : m_field2("lel") {}
virtual ~X() {}
void func1 (std::string s) {
m_field1 += 2;
m_field2 = s;
}
int func2 () {
return m_field1 + 5;
}
void func3 (Y& y) {
int& i = m_field1;
y.m_structfield2 = 1.2f + i++;
}
int func4 () {
func3 (m_field3);
return m_field3.m_structfield1;
}
};
应该return
X::X() -> m_field1 (w), m_field3.m_structfield1 (w), m_field3.m_structfield2 (w)
X::func1(std::string) -> m_field1 (r+w), m_field2 (w)
X::func2() -> m_field1 (r)
X::func3(Y&) -> m_field1 (r+w)
X::func4() -> m_field1 (r+w), m_field3.m_structfield2 (w), m_field3.m_structfield1 (r)
为简单起见,我们可以假设没有继承。
我一直在收集一些 examples of analyzing code with Clang's AST matchers。那里有一个示例应用程序 StructFieldUser,它报告结构的哪些字段被读取或写入,以及每次访问发生在哪个函数中。它与您正在寻找的不同,但它可能是一个有用的参考点。它演示了提取和记录此类信息,并说明了如何将所有部分放在一起。
一般来说,开始使用 AST 匹配器的好地方是 this post by Eli Bendersky。
要了解可以解决您问题的匹配器,您可以练习 clang-query
:
$ clang-query example.cpp -- # the two dashes mean no compilation db
clang-query> let m1 memberExpr()
clang-query> m m1
Match #1:
/path/example.cpp:9:9: note: "root" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
Match #2:
/path/example.cpp:10:9: note: "root" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
...
11 matches.
然后你可以开始使用遍历匹配器连接到其他节点。这使您可以捕获相关的上下文,例如在其中进行引用的函数或 class 方法。将 bind
表达式添加到节点匹配器将帮助您准确了解匹配的内容。绑定节点还将提供对回调中节点的访问权限。
clang-query> let m2 memberExpr(hasAncestor(functionDecl().bind("fdecl"))).bind("mexpr")
clang-query> m m2
Match #1:
/path/example.cpp/path/example.cpp:8:5: note: "fdecl" binds here
Y () {
^~~~~~
/path/example.cpp:9:9: note: "mexpr" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
/path/example.cpp:9:9: note: "root" binds here
m_structfield1 = 0;
^~~~~~~~~~~~~~
Match #2:
/path/example.cpp:8:5: note: "fdecl" binds here
Y () {
^~~~~~
/path/example.cpp:10:9: note: "mexpr" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
/path/example.cpp:10:9: note: "root" binds here
m_structfield2 = 1.0f;
^~~~~~~~~~~~~~
...
可能需要一些工作才能了解如何挑选您需要的确切节点。请注意,上面的匹配器不会在 X::X()
中进行初始化。从
clang-check -ast-dump example.cpp --
表明那些节点不是MemberExpr
节点;它们是 CXXCtorInitializer
个节点,因此需要 cxxCtorInitializer
匹配器来获取这些节点。可能需要多个匹配器才能找到所有不同的节点。