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

kub*_*tto 6 c++ static-analysis clang abstract-syntax-tree llvm-clang

给定一个C++源代码,我想找到每个函数写入和读取的类字段.使用Clang前端的最佳方法是什么?

(我不是要求对所有步骤进行详细解释;但是,有效解决方案的出发点会很棒.)

到目前为止,我尝试使用RecursiveASTVisitor解析语句,但是跟踪节点连接很困难.另外,我无法弄清楚如何跟踪下面的内容:

int& x = m_int_field;
x++;
Run Code Online (Sandbox Code Playgroud)

这明显改变了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;
    }
};
Run Code Online (Sandbox Code Playgroud)

应该回来

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)
Run Code Online (Sandbox Code Playgroud)

我们可以简单地假设没有继承.

Som*_*Tim 6

我一直在收集一些使用Clang的AST匹配器分析代码的示例。那里有一个示例应用程序StructFieldUser,它报告读取或写入结构的哪些字段以及发生每次访问的功能。它与您要查找的内容不同,但这可能是有用的参考点。它演示了如何提取和记录此类信息,并演示了如何将所有信息组合在一起。

通常,从AST匹配器开始的一个好地方是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.
Run Code Online (Sandbox Code Playgroud)

然后,您可以开始使用遍历匹配器连接到其他节点。这使您可以捕获相关的上下文,例如进行引用的函数或类方法。将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;
        ^~~~~~~~~~~~~~
...
Run Code Online (Sandbox Code Playgroud)

学习如何挑选所需的确切节点可能需要花费一些工作。请注意,上面的匹配器不会选择中的初始化X::X()。从看AST

clang-check -ast-dump example.cpp -- 
Run Code Online (Sandbox Code Playgroud)

表明那些节点不是MemberExpr节点;它们是CXXCtorInitializer节点,因此cxxCtorInitializer需要匹配器来获取这些节点。可能需要多个匹配器才能找到所有不同的节点。