xref: /llvm-project/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp (revision e90e43fb9cd1d305f7196cd526aa503374e0f616)
1 //===--- MultipleInheritanceCheck.cpp - clang-tidy-------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "MultipleInheritanceCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang;
14 using namespace clang::ast_matchers;
15 
16 namespace clang::tidy::fuchsia {
17 
18 namespace {
AST_MATCHER(CXXRecordDecl,hasBases)19 AST_MATCHER(CXXRecordDecl, hasBases) {
20   if (Node.hasDefinition())
21     return Node.getNumBases() > 0;
22   return false;
23 }
24 } // namespace
25 
26 // Adds a node (by name) to the interface map, if it was not present in the map
27 // previously.
addNodeToInterfaceMap(const CXXRecordDecl * Node,bool IsInterface)28 void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node,
29                                                      bool IsInterface) {
30   assert(Node->getIdentifier());
31   StringRef Name = Node->getIdentifier()->getName();
32   InterfaceMap.insert(std::make_pair(Name, IsInterface));
33 }
34 
35 // Returns "true" if the boolean "isInterface" has been set to the
36 // interface status of the current Node. Return "false" if the
37 // interface status for the current node is not yet known.
getInterfaceStatus(const CXXRecordDecl * Node,bool & IsInterface) const38 bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node,
39                                                   bool &IsInterface) const {
40   assert(Node->getIdentifier());
41   StringRef Name = Node->getIdentifier()->getName();
42   llvm::StringMapConstIterator<bool> Pair = InterfaceMap.find(Name);
43   if (Pair == InterfaceMap.end())
44     return false;
45   IsInterface = Pair->second;
46   return true;
47 }
48 
isCurrentClassInterface(const CXXRecordDecl * Node) const49 bool MultipleInheritanceCheck::isCurrentClassInterface(
50     const CXXRecordDecl *Node) const {
51   // Interfaces should have no fields.
52   if (!Node->field_empty()) return false;
53 
54   // Interfaces should have exclusively pure methods.
55   return llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) {
56     return M->isUserProvided() && !M->isPureVirtual() && !M->isStatic();
57   });
58 }
59 
isInterface(const CXXRecordDecl * Node)60 bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) {
61   if (!Node->getIdentifier())
62     return false;
63 
64   // Short circuit the lookup if we have analyzed this record before.
65   bool PreviousIsInterfaceResult = false;
66   if (getInterfaceStatus(Node, PreviousIsInterfaceResult))
67     return PreviousIsInterfaceResult;
68 
69   // To be an interface, all base classes must be interfaces as well.
70   for (const auto &I : Node->bases()) {
71     if (I.isVirtual()) continue;
72     const auto *Ty = I.getType()->getAs<RecordType>();
73     if (!Ty) continue;
74     const RecordDecl *D = Ty->getDecl()->getDefinition();
75     if (!D) continue;
76     const auto *Base = cast<CXXRecordDecl>(D);
77     if (!isInterface(Base)) {
78       addNodeToInterfaceMap(Node, false);
79       return false;
80     }
81   }
82 
83   bool CurrentClassIsInterface = isCurrentClassInterface(Node);
84   addNodeToInterfaceMap(Node, CurrentClassIsInterface);
85   return CurrentClassIsInterface;
86 }
87 
registerMatchers(MatchFinder * Finder)88 void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) {
89   // Match declarations which have bases.
90   Finder->addMatcher(cxxRecordDecl(hasBases(), isDefinition()).bind("decl"),
91                      this);
92 }
93 
check(const MatchFinder::MatchResult & Result)94 void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) {
95   if (const auto *D = Result.Nodes.getNodeAs<CXXRecordDecl>("decl")) {
96     // Check against map to see if the class inherits from multiple
97     // concrete classes
98     unsigned NumConcrete = 0;
99     for (const auto &I : D->bases()) {
100       if (I.isVirtual()) continue;
101       const auto *Ty = I.getType()->getAs<RecordType>();
102       if (!Ty) continue;
103       const auto *Base = cast<CXXRecordDecl>(Ty->getDecl()->getDefinition());
104       if (!isInterface(Base)) NumConcrete++;
105     }
106 
107     // Check virtual bases to see if there is more than one concrete
108     // non-virtual base.
109     for (const auto &V : D->vbases()) {
110       const auto *Ty = V.getType()->getAs<RecordType>();
111       if (!Ty) continue;
112       const auto *Base = cast<CXXRecordDecl>(Ty->getDecl()->getDefinition());
113       if (!isInterface(Base)) NumConcrete++;
114     }
115 
116     if (NumConcrete > 1) {
117       diag(D->getBeginLoc(), "inheriting multiple classes that aren't "
118                              "pure virtual is discouraged");
119     }
120   }
121 }
122 
123 } // namespace clang::tidy::fuchsia
124