summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp274
1 files changed, 274 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp
new file mode 100644
index 00000000000..6b3f49879af
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/VirtualNearMissCheck.cpp
@@ -0,0 +1,274 @@
+//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "VirtualNearMissCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace bugprone {
+
+AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
+
+AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
+ return Node.isOverloadedOperator();
+}
+
+/// Finds out if the given method overrides some method.
+static bool isOverrideMethod(const CXXMethodDecl *MD) {
+ return MD->size_overridden_methods() > 0 || MD->hasAttr<OverrideAttr>();
+}
+
+/// Checks whether the return types are covariant, according to
+/// C++[class.virtual]p7.
+///
+/// Similar with clang::Sema::CheckOverridingFunctionReturnType.
+/// \returns true if the return types of BaseMD and DerivedMD are covariant.
+static bool checkOverridingFunctionReturnType(const ASTContext *Context,
+ const CXXMethodDecl *BaseMD,
+ const CXXMethodDecl *DerivedMD) {
+ QualType BaseReturnTy = BaseMD->getType()
+ ->getAs<FunctionType>()
+ ->getReturnType()
+ .getCanonicalType();
+ QualType DerivedReturnTy = DerivedMD->getType()
+ ->getAs<FunctionType>()
+ ->getReturnType()
+ .getCanonicalType();
+
+ if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType())
+ return false;
+
+ // Check if return types are identical.
+ if (Context->hasSameType(DerivedReturnTy, BaseReturnTy))
+ return true;
+
+ /// Check if the return types are covariant.
+
+ // Both types must be pointers or references to classes.
+ if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) &&
+ !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType()))
+ return false;
+
+ /// BTy is the class type in return type of BaseMD. For example,
+ /// B* Base::md()
+ /// While BRD is the declaration of B.
+ QualType DTy = DerivedReturnTy->getPointeeType().getCanonicalType();
+ QualType BTy = BaseReturnTy->getPointeeType().getCanonicalType();
+
+ const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl();
+ const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl();
+ if (DRD == nullptr || BRD == nullptr)
+ return false;
+
+ if (!DRD->hasDefinition() || !BRD->hasDefinition())
+ return false;
+
+ if (DRD == BRD)
+ return true;
+
+ if (!Context->hasSameUnqualifiedType(DTy, BTy)) {
+ // Begin checking whether the conversion from D to B is valid.
+ CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
+ /*DetectVirtual=*/false);
+
+ // Check whether D is derived from B, and fill in a CXXBasePaths object.
+ if (!DRD->isDerivedFrom(BRD, Paths))
+ return false;
+
+ // Check ambiguity.
+ if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType()))
+ return false;
+
+ // Check accessibility.
+ // FIXME: We currently only support checking if B is accessible base class
+ // of D, or D is the same class which DerivedMD is in.
+ bool IsItself =
+ DRD->getCanonicalDecl() == DerivedMD->getParent()->getCanonicalDecl();
+ bool HasPublicAccess = false;
+ for (const auto &Path : Paths) {
+ if (Path.Access == AS_public)
+ HasPublicAccess = true;
+ }
+ if (!HasPublicAccess && !IsItself)
+ return false;
+ // End checking conversion from D to B.
+ }
+
+ // Both pointers or references should have the same cv-qualification.
+ if (DerivedReturnTy.getLocalCVRQualifiers() !=
+ BaseReturnTy.getLocalCVRQualifiers())
+ return false;
+
+ // The class type D should have the same cv-qualification as or less
+ // cv-qualification than the class type B.
+ if (DTy.isMoreQualifiedThan(BTy))
+ return false;
+
+ return true;
+}
+
+/// \returns decayed type for arrays and functions.
+static QualType getDecayedType(QualType Type) {
+ if (const auto *Decayed = Type->getAs<DecayedType>())
+ return Decayed->getDecayedType();
+ return Type;
+}
+
+/// \returns true if the param types are the same.
+static bool checkParamTypes(const CXXMethodDecl *BaseMD,
+ const CXXMethodDecl *DerivedMD) {
+ unsigned NumParamA = BaseMD->getNumParams();
+ unsigned NumParamB = DerivedMD->getNumParams();
+ if (NumParamA != NumParamB)
+ return false;
+
+ for (unsigned I = 0; I < NumParamA; I++) {
+ if (getDecayedType(BaseMD->getParamDecl(I)->getType().getCanonicalType()) !=
+ getDecayedType(
+ DerivedMD->getParamDecl(I)->getType().getCanonicalType()))
+ return false;
+ }
+ return true;
+}
+
+/// \returns true if derived method can override base method except for the
+/// name.
+static bool checkOverrideWithoutName(const ASTContext *Context,
+ const CXXMethodDecl *BaseMD,
+ const CXXMethodDecl *DerivedMD) {
+ if (BaseMD->isStatic() != DerivedMD->isStatic())
+ return false;
+
+ if (BaseMD->getType() == DerivedMD->getType())
+ return true;
+
+ // Now the function types are not identical. Then check if the return types
+ // are covariant and if the param types are the same.
+ if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD))
+ return false;
+ return checkParamTypes(BaseMD, DerivedMD);
+}
+
+/// Check whether BaseMD overrides DerivedMD.
+///
+/// Prerequisite: the class which BaseMD is in should be a base class of that
+/// DerivedMD is in.
+static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD,
+ const CXXMethodDecl *DerivedMD) {
+ for (CXXMethodDecl::method_iterator I = DerivedMD->begin_overridden_methods(),
+ E = DerivedMD->end_overridden_methods();
+ I != E; ++I) {
+ const CXXMethodDecl *OverriddenMD = *I;
+ if (BaseMD->getCanonicalDecl() == OverriddenMD->getCanonicalDecl())
+ return true;
+ }
+
+ return false;
+}
+
+bool VirtualNearMissCheck::isPossibleToBeOverridden(
+ const CXXMethodDecl *BaseMD) {
+ auto Iter = PossibleMap.find(BaseMD);
+ if (Iter != PossibleMap.end())
+ return Iter->second;
+
+ bool IsPossible = !BaseMD->isImplicit() && !isa<CXXConstructorDecl>(BaseMD) &&
+ !isa<CXXDestructorDecl>(BaseMD) && BaseMD->isVirtual() &&
+ !BaseMD->isOverloadedOperator() &&
+ !isa<CXXConversionDecl>(BaseMD);
+ PossibleMap[BaseMD] = IsPossible;
+ return IsPossible;
+}
+
+bool VirtualNearMissCheck::isOverriddenByDerivedClass(
+ const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) {
+ auto Key = std::make_pair(BaseMD, DerivedRD);
+ auto Iter = OverriddenMap.find(Key);
+ if (Iter != OverriddenMap.end())
+ return Iter->second;
+
+ bool IsOverridden = false;
+ for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) {
+ if (!isOverrideMethod(DerivedMD))
+ continue;
+
+ if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) {
+ IsOverridden = true;
+ break;
+ }
+ }
+ OverriddenMap[Key] = IsOverridden;
+ return IsOverridden;
+}
+
+void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) {
+ if (!getLangOpts().CPlusPlus)
+ return;
+
+ Finder->addMatcher(
+ cxxMethodDecl(
+ unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(),
+ cxxDestructorDecl(), cxxConversionDecl(), isStatic(),
+ isOverloadedOperator())))
+ .bind("method"),
+ this);
+}
+
+void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *DerivedMD = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
+ assert(DerivedMD);
+
+ const ASTContext *Context = Result.Context;
+
+ const auto *DerivedRD = DerivedMD->getParent()->getDefinition();
+ assert(DerivedRD);
+
+ for (const auto &BaseSpec : DerivedRD->bases()) {
+ if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) {
+ for (const auto *BaseMD : BaseRD->methods()) {
+ if (!isPossibleToBeOverridden(BaseMD))
+ continue;
+
+ if (isOverriddenByDerivedClass(BaseMD, DerivedRD))
+ continue;
+
+ unsigned EditDistance = BaseMD->getName().edit_distance(
+ DerivedMD->getName(), EditDistanceThreshold);
+ if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) {
+ if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) {
+ // A "virtual near miss" is found.
+ auto Range = CharSourceRange::getTokenRange(
+ SourceRange(DerivedMD->getLocation()));
+
+ bool ApplyFix = !BaseMD->isTemplateInstantiation() &&
+ !DerivedMD->isTemplateInstantiation();
+ auto Diag =
+ diag(DerivedMD->getLocStart(),
+ "method '%0' has a similar name and the same signature as "
+ "virtual method '%1'; did you mean to override it?")
+ << DerivedMD->getQualifiedNameAsString()
+ << BaseMD->getQualifiedNameAsString();
+ if (ApplyFix)
+ Diag << FixItHint::CreateReplacement(Range, BaseMD->getName());
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace bugprone
+} // namespace tidy
+} // namespace clang
OpenPOWER on IntegriCloud