This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] performance-* checks: Match AllowedTypes against qualified type names when they contain "::".
ClosedPublic

Authored by flx on Mar 16 2021, 1:13 PM.

Details

Summary

This allows users to be more precise and exclude a type in a specific namespace
from triggering the check instead of excluding all types with the same
unqualified name.

This change should not interfere with correctly configured clang-tidy setups
since an AllowedType with "::" would never match.

Diff Detail

Event Timeline

flx created this revision.Mar 16 2021, 1:13 PM
flx requested review of this revision.Mar 16 2021, 1:13 PM
Herald added a project: Restricted Project. · View Herald TranscriptMar 16 2021, 1:13 PM
flx added a subscriber: ckennelly.Mar 16 2021, 1:14 PM
ymandel accepted this revision.Mar 17 2021, 6:29 AM

(but please wait for "accept" from an owner).

clang-tools-extra/clang-tidy/utils/Matchers.h
74

nit: while this change is not much worse than the existing code, the matcher really should perform this test for "::", and create the Regex, for each string just once. Regex construction is relatively expensive, and matchers of this sort (very generic) are often called quite frequently.

for that matter, this matcher is now really close to matchesName and would probably be best as a first-class matcher, rather than limited to clang-tidy/utils. But, I'll admit that such is beyond the scope of this patch.

This revision is now accepted and ready to land.Mar 17 2021, 6:29 AM
flx added inline comments.Mar 17 2021, 7:05 AM
clang-tools-extra/clang-tidy/utils/Matchers.h
74

It wouldn't be hard to change it something like this:

struct MatcherPair {                                                                                                       
    llvm::Regex regex;                                                                                                       
    bool matchQualifiedName = false;                                                                                         
  };                                                                                                                         
  std::vector<MatcherPair> Matchers;                                                                                         
  std::transform(                                                                                                            
      NameList.begin(), NameList.end(), std::back_inserter(Matchers),                                                        
      [](const std::string& Name) {                                                                                          
        return MatcherPair{                                                                                                  
            .regex = llvm::Regex(Name),                                                                                      
            .matchQualifiedName = Name.find("::") != std::string::npos,                                                      
        };                                                                                                                   
      });                                                                                                                    
  return llvm::any_of(Matchers, [&Node](const MatcherPair& Matcher) {                                                        
    if (Matcher.matchQualifiedName) {                                                                                        
      return Matcher.regex.match(Node.getQualifiedNameAsString());                                                           
    }                                                                                                                        
    return Matcher.regex.match(Node.getName());                                                                              
  });

Is this what you had in mind?

ymandel added inline comments.Mar 17 2021, 7:17 AM
clang-tools-extra/clang-tidy/utils/Matchers.h
74

Thanks, that's almost it. The key point is that the code before the return be executed once, during matcher construction, and not each time match is called. You could define your own class (instead of using the macro) or just your own factory function:

struct MatcherPair {                                                                                                       
    llvm::Regex regex;                                                                                                       
    bool matchQualifiedName = false;                                                                                         
};        

AST_MATCHER_P(NamedDecl, matchesAnyListedNameImpl, std::vector<MatcherPair>, Matchers) {
  return llvm::any_of(Matchers, [&Node](const MatcherPair& Matcher) {                                                        
    if (Matcher.matchQualifiedName) {                                                                                        
      return Matcher.regex.match(Node.getQualifiedNameAsString());                                                           
    }                                                                                                                        
    return Matcher.regex.match(Node.getName());                                                                              
  });
}

Matcher<NamedDecl> matchesAnyListedName(std::vector<std::string> NameList) {
  std::vector<MatcherPair> Matchers;                                                                                         
  std::transform(                                                                                                            
      NameList.begin(), NameList.end(), std::back_inserter(Matchers),                                                        
      [](const std::string& Name) {                                                                                          
        return MatcherPair{                                                                                                  
            .regex = llvm::Regex(Name),                                                                                      
            .matchQualifiedName = Name.find("::") != std::string::npos,                                                      
        };                                                                                                                   
      });
  return matchesAnyListNameImpl(std::move(Matchers));
}
flx added inline comments.Mar 17 2021, 8:38 AM
clang-tools-extra/clang-tidy/utils/Matchers.h
74

Thanks for the snippet! I tried, but ran into the issue that llvm::Regex can only be moved, but AST_MATCHER_P uses copy construction of its param somewhere. I was able to work around it by using shared ptrs:

struct RegexAndScope {                                                                                                       
  llvm::Regex regex;                                                                                                         
  bool matchQualifiedName = false;                                                                                           
};                                                                                                                           
                                                                                                                             
AST_MATCHER_P(NamedDecl, matchesAnyListedNameImpl,                                                                           
              std::vector<std::shared_ptr<RegexAndScope>>, Matchers) {                                                       
  return llvm::any_of(                                                                                                       
      Matchers, [&Node](const std::shared_ptr<RegexAndScope>& Matcher) {                                                     
        if (Matcher->matchQualifiedName) {                                                                                   
          return Matcher->regex.match(Node.getQualifiedNameAsString());                                                      
        }                                                                                                                    
        return Matcher->regex.match(Node.getName());                                                                         
      });                                                                                                                    
}                                                                                                                            
                                                                                                                             
ast_matchers::internal::Matcher<NamedDecl> matchesAnyListedName(                                                             
    const std::vector<std::string>& NameList) {                                                                              
  std::vector<std::shared_ptr<RegexAndScope>> Matchers;                                                                      
  std::transform(NameList.begin(), NameList.end(), std::back_inserter(Matchers),                                             
                 [](const std::string& Name) {                                                                               
                   auto RAS = std::make_shared<RegexAndScope>();                                                             
                   RAS->regex = llvm::Regex(Name);                                                                           
                   RAS->matchQualifiedName =                                                                                 
                       Name.find("::") != std::string::npos;                                                                 
                   return RAS;                                                                                               
                 });                                                                                                         
  return matchesAnyListedNameImpl(std::move(Matchers));                                                                      
}

What do you think?

ymandel added inline comments.Mar 17 2021, 9:10 AM
clang-tools-extra/clang-tidy/utils/Matchers.h
74

That works for me, but let's see what other reviewers think. Personally, I'd just define the class directly at this point:

class MatchesAnyListedNameMatcher
    : public ast_matchers::internal::MatcherInterface<NamedDecl> {
 public:
  explicit MatchesAnyListedNameMatcher(llvm::ArrayRef<std::string> NameList) {
    std::transform(
        NameList.begin(), NameList.end(), std::back_inserter(Matchers),
        [](const std::string& Name) {
          return MatcherPair{
              .regex = llvm::Regex(Name),
              .matchQualifiedName = Name.find("::") != std::string::npos,
          };
        });
  }
  bool matches(
      const NamedDecl& Node, ast_matchers::internal::ASTMatchFinder* Finder,
      ast_matchers::internal::BoundNodesTreeBuilder* Builder) const override {
    return llvm::any_of(Matchers, [&Node](const MatcherPair& Matcher) {
      if (Matcher.matchQualifiedName) {
        return Matcher.regex.match(Node.getQualifiedNameAsString());
      }
      return Matcher.regex.match(Node.getName());
    });
  }

 private:
  std::vector<MatcherPair> Matchers;
};

inline ::clang::ast_matchers::internal::Matcher<Type> matchesAnyListedName(
    llvm::ArrayRef<std::string> NameList) {
  return ::clang::ast_matchers::internal::makeMatcher(
      new MatchesAnyListedNameMatcher(NameList))
}
hokein added inline comments.Mar 17 2021, 1:31 PM
clang-tools-extra/clang-tidy/utils/Matchers.h
51–52

worth some comments on this.

73–91

If we pass the name as a llvm::StringRef, we can use if (Name.contains("::")) which seems a bit clearer to me.

73–91

there is a tricky case where the :: is a prefix of the Name, e.g. a global symbol x, the Name is ::x, and getQualifiedNameAsString of symbol x is x (without the :: prefix), then the matcher will not find a match, but we expect this is a match right?

74

+1 the idea looks good to me.

flx updated this revision to Diff 331941.Mar 19 2021, 11:01 AM

Applied changes suggested by ymandel, thanks!

flx marked 6 inline comments as done.Mar 19 2021, 11:12 AM
flx added inline comments.
clang-tools-extra/clang-tidy/utils/Matchers.h
51–52

Done in its new location.

73–91

I agree that's a tricky case.

We could prepend "::" to getQualifiedNameAsString() and always match against it. Would this work?

Or we could do this only for regex names that have a leading "::". This would avoid extra string operations, and users could still match using "^foo::Bar$" to match ::foo::Bar.

ymandel added inline comments.Mar 22 2021, 5:32 AM
clang-tools-extra/clang-tidy/utils/Matchers.h
60

I don't think that the clang style-guide accepts designated initializers yet.

73–91

Or we could do this only for regex names that have a leading "::". This would avoid extra string operations, and users could still match using "^foo::Bar$" to match ::foo::Bar.

This should work most of the time. There could still be mistakes, like if a user spells the "::" in some odd way, like "[:][:]", but that seems quite unlikely in practice.

79

Worth a comment i think.

86

nit: s/NamedDecls/NamedDecl's/

flx updated this revision to Diff 335267.Apr 5 2021, 8:53 AM
flx marked an inline comment as done.

Create a NameMatcher class that handles matching against the best name variant
(unqualified, qualified, fully qualified) of the NamedDecl.

flx marked 2 inline comments as done.Apr 5 2021, 8:54 AM

Thanks for the comments, PTAL!

clang-tools-extra/clang-tidy/utils/Matchers.h
79

Done by adding comments to each match mode.

ymandel accepted this revision.Apr 5 2021, 3:29 PM