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 { |
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. |
28 | void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node, |
29 | bool IsInterface) { |
30 | assert(Node->getIdentifier()); |
31 | StringRef Name = Node->getIdentifier()->getName(); |
32 | InterfaceMap.insert(KV: std::make_pair(x&: Name, y&: 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. |
38 | 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(Key: Name); |
43 | if (Pair == InterfaceMap.end()) |
44 | return false; |
45 | IsInterface = Pair->second; |
46 | return true; |
47 | } |
48 | |
49 | bool MultipleInheritanceCheck::isCurrentClassInterface( |
50 | const CXXRecordDecl *Node) const { |
51 | // Interfaces should have no fields. |
52 | if (!Node->field_empty()) |
53 | return false; |
54 | |
55 | // Interfaces should have exclusively pure methods. |
56 | return llvm::none_of(Range: Node->methods(), P: [](const CXXMethodDecl *M) { |
57 | return M->isUserProvided() && !M->isPureVirtual() && !M->isStatic(); |
58 | }); |
59 | } |
60 | |
61 | bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) { |
62 | if (!Node->getIdentifier()) |
63 | return false; |
64 | |
65 | // Short circuit the lookup if we have analyzed this record before. |
66 | bool PreviousIsInterfaceResult = false; |
67 | if (getInterfaceStatus(Node, IsInterface&: PreviousIsInterfaceResult)) |
68 | return PreviousIsInterfaceResult; |
69 | |
70 | // To be an interface, all base classes must be interfaces as well. |
71 | for (const auto &I : Node->bases()) { |
72 | if (I.isVirtual()) |
73 | continue; |
74 | const auto *Ty = I.getType()->getAs<RecordType>(); |
75 | if (!Ty) |
76 | continue; |
77 | const RecordDecl *D = Ty->getDecl()->getDefinition(); |
78 | if (!D) |
79 | continue; |
80 | const auto *Base = cast<CXXRecordDecl>(Val: D); |
81 | if (!isInterface(Node: Base)) { |
82 | addNodeToInterfaceMap(Node, IsInterface: false); |
83 | return false; |
84 | } |
85 | } |
86 | |
87 | bool CurrentClassIsInterface = isCurrentClassInterface(Node); |
88 | addNodeToInterfaceMap(Node, IsInterface: CurrentClassIsInterface); |
89 | return CurrentClassIsInterface; |
90 | } |
91 | |
92 | void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) { |
93 | // Match declarations which have bases. |
94 | Finder->addMatcher(NodeMatch: cxxRecordDecl(hasBases(), isDefinition()).bind(ID: "decl" ), |
95 | Action: this); |
96 | } |
97 | |
98 | void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { |
99 | if (const auto *D = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "decl" )) { |
100 | // Check against map to see if the class inherits from multiple |
101 | // concrete classes |
102 | unsigned NumConcrete = 0; |
103 | for (const auto &I : D->bases()) { |
104 | if (I.isVirtual()) |
105 | continue; |
106 | const auto *Ty = I.getType()->getAs<RecordType>(); |
107 | if (!Ty) |
108 | continue; |
109 | const auto *Base = cast<CXXRecordDecl>(Val: Ty->getDecl()->getDefinition()); |
110 | if (!isInterface(Node: Base)) |
111 | NumConcrete++; |
112 | } |
113 | |
114 | // Check virtual bases to see if there is more than one concrete |
115 | // non-virtual base. |
116 | for (const auto &V : D->vbases()) { |
117 | const auto *Ty = V.getType()->getAs<RecordType>(); |
118 | if (!Ty) |
119 | continue; |
120 | const auto *Base = cast<CXXRecordDecl>(Val: Ty->getDecl()->getDefinition()); |
121 | if (!isInterface(Node: Base)) |
122 | NumConcrete++; |
123 | } |
124 | |
125 | if (NumConcrete > 1) { |
126 | diag(D->getBeginLoc(), "inheriting multiple classes that aren't " |
127 | "pure virtual is discouraged" ); |
128 | } |
129 | } |
130 | } |
131 | |
132 | } // namespace clang::tidy::fuchsia |
133 | |