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()) return false; |
53 | |
54 | // Interfaces should have exclusively pure methods. |
55 | return llvm::none_of(Range: Node->methods(), P: [](const CXXMethodDecl *M) { |
56 | return M->isUserProvided() && !M->isPureVirtual() && !M->isStatic(); |
57 | }); |
58 | } |
59 | |
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, IsInterface&: 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>(Val: D); |
77 | if (!isInterface(Node: Base)) { |
78 | addNodeToInterfaceMap(Node, IsInterface: false); |
79 | return false; |
80 | } |
81 | } |
82 | |
83 | bool CurrentClassIsInterface = isCurrentClassInterface(Node); |
84 | addNodeToInterfaceMap(Node, IsInterface: CurrentClassIsInterface); |
85 | return CurrentClassIsInterface; |
86 | } |
87 | |
88 | void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) { |
89 | // Match declarations which have bases. |
90 | Finder->addMatcher(NodeMatch: cxxRecordDecl(hasBases(), isDefinition()).bind(ID: "decl" ), |
91 | Action: this); |
92 | } |
93 | |
94 | void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { |
95 | if (const auto *D = Result.Nodes.getNodeAs<CXXRecordDecl>(ID: "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>(Val: Ty->getDecl()->getDefinition()); |
104 | if (!isInterface(Node: 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>(Val: Ty->getDecl()->getDefinition()); |
113 | if (!isInterface(Node: 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 | |