【问题标题】:How can I find all member field read/writes using Clang?如何使用 Clang 查找所有成员字段读/写?
【发布时间】:2017-09-09 07:17:08
【问题描述】:

给定一个 C++ 源代码,我想找到每个函数写入和读取的类字段。使用 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)

为了简单起见,我们可以假设没有继承。

【问题讨论】:

    标签: c++ clang abstract-syntax-tree static-analysis llvm-clang


    【解决方案1】:

    我一直在收集一些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 匹配器来获取这些节点。可能需要多个匹配器来查找所有不同的节点。

    【讨论】:

      猜你喜欢
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      • 2011-09-10
      • 1970-01-01
      • 2013-12-29
      • 2020-08-04
      • 2012-11-01
      相关资源
      最近更新 更多