blob: ae3222a2bd1a40aee156d163510ced8510e778e3 [file] [log] [blame]
//===--- MultipleInheritanceCheck.cpp - clang-tidy-------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "MultipleInheritanceCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang;
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace fuchsia {
namespace {
AST_MATCHER(CXXRecordDecl, hasBases) {
if (Node.hasDefinition())
return Node.getNumBases() > 0;
return false;
}
} // namespace
// Adds a node (by name) to the interface map, if it was not present in the map
// previously.
void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node,
bool IsInterface) {
assert(Node->getIdentifier());
StringRef Name = Node->getIdentifier()->getName();
InterfaceMap.insert(std::make_pair(Name, IsInterface));
}
// Returns "true" if the boolean "isInterface" has been set to the
// interface status of the current Node. Return "false" if the
// interface status for the current node is not yet known.
bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node,
bool &IsInterface) const {
assert(Node->getIdentifier());
StringRef Name = Node->getIdentifier()->getName();
llvm::StringMapConstIterator<bool> Pair = InterfaceMap.find(Name);
if (Pair == InterfaceMap.end())
return false;
IsInterface = Pair->second;
return true;
}
bool MultipleInheritanceCheck::isCurrentClassInterface(
const CXXRecordDecl *Node) const {
// Interfaces should have no fields.
if (!Node->field_empty()) return false;
// Interfaces should have exclusively pure methods.
return llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) {
return M->isUserProvided() && !M->isPure() && !M->isStatic();
});
}
bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) {
if (!Node->getIdentifier())
return false;
// Short circuit the lookup if we have analyzed this record before.
bool PreviousIsInterfaceResult;
if (getInterfaceStatus(Node, PreviousIsInterfaceResult))
return PreviousIsInterfaceResult;
// To be an interface, all base classes must be interfaces as well.
for (const auto &I : Node->bases()) {
if (I.isVirtual()) continue;
const auto *Ty = I.getType()->getAs<RecordType>();
if (!Ty) continue;
const RecordDecl *D = Ty->getDecl()->getDefinition();
if (!D) continue;
const auto *Base = cast<CXXRecordDecl>(D);
if (!isInterface(Base)) {
addNodeToInterfaceMap(Node, false);
return false;
}
}
bool CurrentClassIsInterface = isCurrentClassInterface(Node);
addNodeToInterfaceMap(Node, CurrentClassIsInterface);
return CurrentClassIsInterface;
}
void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) {
// Match declarations which have bases.
Finder->addMatcher(
cxxRecordDecl(allOf(hasBases(), isDefinition())).bind("decl"), this);
}
void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *D = Result.Nodes.getNodeAs<CXXRecordDecl>("decl")) {
// Check against map to see if if the class inherits from multiple
// concrete classes
unsigned NumConcrete = 0;
for (const auto &I : D->bases()) {
if (I.isVirtual()) continue;
const auto *Ty = I.getType()->getAs<RecordType>();
if (!Ty) continue;
const auto *Base = cast<CXXRecordDecl>(Ty->getDecl()->getDefinition());
if (!isInterface(Base)) NumConcrete++;
}
// Check virtual bases to see if there is more than one concrete
// non-virtual base.
for (const auto &V : D->vbases()) {
const auto *Ty = V.getType()->getAs<RecordType>();
if (!Ty) continue;
const auto *Base = cast<CXXRecordDecl>(Ty->getDecl()->getDefinition());
if (!isInterface(Base)) NumConcrete++;
}
if (NumConcrete > 1) {
diag(D->getBeginLoc(), "inheriting multiple classes that aren't "
"pure virtual is discouraged");
}
}
}
} // namespace fuchsia
} // namespace tidy
} // namespace clang