如何查找使用Clang的所有成员字段读/写?

问题描述:

给定一个C++源代码,我想找到每个函数写入和读取的类字段。使用Clang前端进行此操作的最佳方式是什么?如何查找使用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; 
    } 
}; 

应该返回

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. 

然后你就可以开始连接到使用穿越的匹配其他节点。这使您可以捕获相关的上下文,如引用的函数或类方法。将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匹配器来获得这些节点。可能需要多个匹配器才能找到所有不同的节点。