Search code examples
clangllvmclang-ast-matchers

How to match a struct member access using clang AST matcher?


I am trying to write a clang-tidy check to rename struct members. For this, I need to match MemberExpr nodes that access a certain member, from certain structs.

In the following code, it should only match all uses of member on types S1 and S2 (8 matches in total).

typedef struct S1 { int member; int v; } S1; typedef struct MS1 {S1 s1; } MS1;
typedef struct S2 { int member; int v; } S2; typedef struct MS2 {S2 s2; } MS2;
typedef struct S3 { int member; int v; } S3; typedef struct MS3 {S2 s3; } MS3;

void f() {
  S1 *s1a, s1b; MS1 *ms1a, ms1b;
  S2 *s2a, s2b; MS2 *ms2a, ms2b;
  S3 *s3a, s3b; MS3 *ms3a, ms3b;

  (void)s1a->member; (void)s1b.member; (void)ms1a->s1.member; (void)ms1b.s1.member;
  (void)s2a->member; (void)s2b.member; (void)ms2a->s2.member; (void)ms2b.s2.member;
  (void)s3a->member; (void)s3b.member; (void)ms3a->s3.member; (void)ms3b.s3.member;

  (void)s1a->v; (void)s1b.v; (void)ms1a->s1.v; (void)ms1b.s1.v;
  (void)s2a->v; (void)s2b.v; (void)ms2a->s2.v; (void)ms2b.s2.v;
  (void)s3a->v; (void)s3b.v; (void)ms3a->s3.v; (void)ms3b.s3.v;
}

The matcher memberExpr(member(hasName("member"))) is too broad and also includes type S3.

How can I limit the matcher to only return those member accesses of S1 and S2? Thanks.


Solution

  • Perhaps surprisingly, the hasName selector can be used to restrict the member's containing class or namespace as well as its simple identifier name. Quoting the linked documentation:

    Matches NamedDecl nodes that have the specified name.
    
    Supports specifying enclosing namespaces or classes by prefixing the name
    with '<enclosing>::'.
    Does not match typedefs of an underlying type with the given name.
    
    Example matches X (Name == "X")
      class X;
    
    Example matches X (Name is one of "::a::b::X", "a::b::X", "b::X", "X")
      namespace a { namespace b { class X; } }
    

    This means we can match S1::member and S2::member like this:

    memberExpr(member(anyOf(hasName("S1::member"),hasName("S2::member"))))
    

    We can also use matchesName to express the conditionality using a regular expression:

    memberExpr(member(matchesName("S[12]::member")))
    

    With either of the above, I get eight matches on your testcase, but only after fixing the apparent typo in this line:

    typedef struct S3 { int member; int v; } S3; typedef struct MS3 {S2 s3; } MS3;
    

    which I think should be:

    typedef struct S3 { int member; int v; } S3; typedef struct MS3 {S3 s3; } MS3;
                                                                      ^